2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
22 #include "audio_mapping_view.h"
23 #include "check_box.h"
24 #include "config_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "nag_dialog.h"
27 #include "static_text.h"
28 #include "wx_variant.h"
29 #include "lib/constants.h"
31 #include <dcp/filesystem.h>
32 #include <dcp/raw_convert.h>
37 using std::make_shared;
40 using std::shared_ptr;
44 using boost::optional;
45 #if BOOST_VERSION >= 106100
46 using namespace boost::placeholders;
57 Page::Page (wxSize panel_size, int border)
60 , _panel_size (panel_size)
61 , _window_exists (false)
63 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
68 Page::CreateWindow (wxWindow* parent)
70 return create_window (parent);
75 Page::create_window (wxWindow* parent)
77 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
78 auto s = new wxBoxSizer (wxVERTICAL);
82 _window_exists = true;
85 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
91 Page::config_changed_wrapper ()
99 Page::window_destroyed ()
101 _window_exists = false;
105 GeneralPage::GeneralPage (wxSize panel_size, int border)
106 : Page (panel_size, border)
113 GeneralPage::GetName () const
120 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
122 _set_language = new CheckBox (_panel, _("Set language"));
123 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
124 _language = new wxChoice (_panel, wxID_ANY);
125 vector<pair<string, string>> languages;
126 languages.push_back (make_pair("Čeština", "cs_CZ"));
127 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
128 languages.push_back (make_pair("Dansk", "da_DK"));
129 languages.push_back (make_pair("Deutsch", "de_DE"));
130 languages.push_back (make_pair("English", "en_GB"));
131 languages.push_back (make_pair("Español", "es_ES"));
132 languages.push_back (make_pair("فارسی", "fa_IR"));
133 languages.push_back (make_pair("Français", "fr_FR"));
134 languages.push_back (make_pair("Italiano", "it_IT"));
135 languages.push_back (make_pair("ქართული", "ka_KA"));
136 languages.push_back (make_pair("Nederlands", "nl_NL"));
137 languages.push_back (make_pair("Русский", "ru_RU"));
138 languages.push_back (make_pair("Polski", "pl_PL"));
139 languages.push_back (make_pair("Português europeu", "pt_PT"));
140 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
141 languages.push_back (make_pair("Svenska", "sv_SE"));
142 languages.push_back (make_pair("Slovenščina", "sl_SI"));
143 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
144 // languages.push_back (make_pair("Türkçe", "tr_TR"));
145 languages.push_back (make_pair("українська мова", "uk_UA"));
146 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
147 checked_set (_language, languages);
148 table->Add (_language, wxGBPosition (r, 1));
151 auto restart = add_label_to_sizer (
152 table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to see language changes)")), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
154 wxFont font = restart->GetFont();
155 font.SetStyle (wxFONTSTYLE_ITALIC);
156 font.SetPointSize (font.GetPointSize() - 1);
157 restart->SetFont (font);
160 _set_language->bind(&GeneralPage::set_language_changed, this);
161 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
165 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
167 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
168 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
171 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
172 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
175 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
176 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
180 GeneralPage::config_changed ()
182 auto config = Config::instance ();
184 checked_set (_set_language, static_cast<bool>(config->language()));
186 /* Backwards compatibility of config file */
188 map<string, string> compat_map;
189 compat_map["fr"] = "fr_FR";
190 compat_map["it"] = "it_IT";
191 compat_map["es"] = "es_ES";
192 compat_map["sv"] = "sv_SE";
193 compat_map["de"] = "de_DE";
194 compat_map["nl"] = "nl_NL";
195 compat_map["ru"] = "ru_RU";
196 compat_map["pl"] = "pl_PL";
197 compat_map["da"] = "da_DK";
198 compat_map["pt"] = "pt_PT";
199 compat_map["sk"] = "sk_SK";
200 compat_map["cs"] = "cs_CZ";
201 compat_map["uk"] = "uk_UA";
203 auto lang = config->language().get_value_or("en_GB");
204 if (compat_map.find(lang) != compat_map.end ()) {
205 lang = compat_map[lang];
208 checked_set (_language, lang);
210 checked_set (_check_for_updates, config->check_for_updates ());
211 checked_set (_check_for_test_updates, config->check_for_test_updates ());
213 setup_sensitivity ();
217 GeneralPage::setup_sensitivity ()
219 _language->Enable (_set_language->GetValue ());
220 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
224 GeneralPage::set_language_changed ()
226 setup_sensitivity ();
227 if (_set_language->GetValue ()) {
230 Config::instance()->unset_language ();
235 GeneralPage::language_changed ()
237 int const sel = _language->GetSelection ();
239 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
241 Config::instance()->unset_language ();
246 GeneralPage::check_for_updates_changed ()
248 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
252 GeneralPage::check_for_test_updates_changed ()
254 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
257 CertificateChainEditor::CertificateChainEditor (
261 function<void (shared_ptr<dcp::CertificateChain>)> set,
262 function<shared_ptr<const dcp::CertificateChain> (void)> get,
263 function<bool (void)> nag_alter
265 : wxDialog (parent, wxID_ANY, title)
268 , _nag_alter (nag_alter)
270 _sizer = new wxBoxSizer (wxVERTICAL);
272 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
273 _sizer->Add (certificates_sizer, 0, wxALL, border);
275 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
280 ip.SetText (_("Type"));
282 _certificates->InsertColumn (0, ip);
288 ip.SetText (_("Thumbprint"));
291 wxFont font = ip.GetFont ();
292 font.SetFamily (wxFONTFAMILY_TELETYPE);
295 _certificates->InsertColumn (1, ip);
298 certificates_sizer->Add (_certificates, 1, wxEXPAND);
301 auto s = new wxBoxSizer (wxVERTICAL);
302 _add_certificate = new Button (this, _("Add..."));
303 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304 _remove_certificate = new Button (this, _("Remove"));
305 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
306 _export_certificate = new Button (this, _("Export certificate..."));
307 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
308 _export_chain = new Button (this, _("Export chain..."));
309 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
310 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
313 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
314 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
317 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
318 _private_key = new StaticText (this, wxT(""));
319 wxFont font = _private_key->GetFont ();
320 font.SetFamily (wxFONTFAMILY_TELETYPE);
321 _private_key->SetFont (font);
322 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
323 _import_private_key = new Button (this, _("Import..."));
324 table->Add (_import_private_key, wxGBPosition (r, 2));
325 _export_private_key = new Button (this, _("Export..."));
326 table->Add (_export_private_key, wxGBPosition (r, 3));
329 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
330 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
331 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
332 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
335 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
336 font = *wxSMALL_FONT;
337 font.SetWeight (wxFONTWEIGHT_BOLD);
338 _private_key_bad->SetFont (font);
339 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
342 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
343 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
344 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
345 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
346 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
347 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
348 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
349 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
350 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
352 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
354 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
357 SetSizerAndFit (_sizer);
359 update_certificate_list ();
360 update_private_key ();
361 update_sensitivity ();
365 CertificateChainEditor::add_button (wxWindow* button)
367 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
372 CertificateChainEditor::add_certificate ()
374 auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
376 if (d->ShowModal() == wxID_OK) {
381 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
382 } catch (boost::filesystem::filesystem_error& e) {
383 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
387 if (!extra.empty ()) {
390 _("This file contains other certificates (or other data) after its first certificate. "
391 "Only the first certificate will be used.")
394 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
396 if (!chain->chain_valid ()) {
399 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
400 "Add certificates in order from root to intermediate to leaf.")
405 update_certificate_list ();
407 } catch (dcp::MiscError& e) {
408 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
412 update_sensitivity ();
416 CertificateChainEditor::remove_certificate ()
419 /* Cancel was clicked */
423 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
428 _certificates->DeleteItem (i);
429 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
433 update_sensitivity ();
434 update_certificate_list ();
438 CertificateChainEditor::export_certificate ()
440 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
445 auto all = _get()->root_to_leaf();
447 wxString default_name;
449 default_name = "root.pem";
450 } else if (i == static_cast<int>(all.size() - 1)) {
451 default_name = "leaf.pem";
453 default_name = "intermediate.pem";
456 auto d = make_wx<wxFileDialog>(
457 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
458 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
461 auto j = all.begin ();
462 for (int k = 0; k < i; ++k) {
466 if (d->ShowModal() != wxID_OK) {
470 boost::filesystem::path path(wx_to_std(d->GetPath()));
471 if (path.extension() != ".pem") {
474 dcp::File f(path, "w");
476 throw OpenFileError(path, errno, OpenFileError::WRITE);
479 string const s = j->certificate(true);
480 f.checked_write(s.c_str(), s.length());
484 CertificateChainEditor::export_chain ()
486 auto d = make_wx<wxFileDialog>(
487 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
488 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
491 if (d->ShowModal() != wxID_OK) {
495 boost::filesystem::path path(wx_to_std(d->GetPath()));
496 if (path.extension() != ".pem") {
499 dcp::File f(path, "w");
501 throw OpenFileError(path, errno, OpenFileError::WRITE);
504 auto const s = _get()->chain();
505 f.checked_write(s.c_str(), s.length());
509 CertificateChainEditor::update_certificate_list ()
511 _certificates->DeleteAllItems ();
513 auto certs = _get()->root_to_leaf();
514 for (auto const& i: certs) {
517 _certificates->InsertItem (item);
518 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
521 _certificates->SetItem (n, 0, _("Root"));
522 } else if (n == (certs.size() - 1)) {
523 _certificates->SetItem (n, 0, _("Leaf"));
525 _certificates->SetItem (n, 0, _("Intermediate"));
531 static wxColour normal = _private_key_bad->GetForegroundColour ();
533 if (_get()->private_key_valid()) {
534 _private_key_bad->Hide ();
535 _private_key_bad->SetForegroundColour (normal);
537 _private_key_bad->Show ();
538 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
543 CertificateChainEditor::remake_certificates ()
546 /* Cancel was clicked */
550 auto d = make_wx<MakeChainDialog>(this, _get());
552 if (d->ShowModal () == wxID_OK) {
554 update_certificate_list ();
555 update_private_key ();
560 CertificateChainEditor::update_sensitivity ()
562 /* We can only remove the leaf certificate */
563 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
564 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
568 CertificateChainEditor::update_private_key ()
570 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
575 CertificateChainEditor::import_private_key ()
577 auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
579 if (d->ShowModal() == wxID_OK) {
581 boost::filesystem::path p (wx_to_std (d->GetPath ()));
582 if (dcp::filesystem::file_size(p) > 8192) {
585 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
590 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
591 chain->set_key (dcp::file_to_string (p));
593 update_private_key ();
594 } catch (std::exception& e) {
595 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 = make_wx<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());
631 KeysPage::GetName () const
639 wxFont subheading_font (*wxNORMAL_FONT);
640 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
642 auto sizer = _panel->GetSizer();
645 auto m = new StaticText (_panel, _("Decrypting KDMs"));
646 m->SetFont (subheading_font);
647 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
650 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
652 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
653 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
655 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
657 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
658 auto decryption_advanced = new Button (_panel, _("Advanced..."));
659 kdm_buttons->Add (decryption_advanced, 0);
661 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
663 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
664 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
665 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
666 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
669 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
670 m->SetFont (subheading_font);
671 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
674 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
676 auto signing_advanced = new Button (_panel, _("Advanced..."));
677 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
678 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
679 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
681 sizer->Add (signing_buttons, 0, wxLEFT, _border);
683 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
684 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
689 KeysPage::remake_signing ()
691 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
693 if (d->ShowModal () == wxID_OK) {
694 Config::instance()->set_signer_chain(d->get());
700 KeysPage::decryption_advanced ()
702 auto c = new CertificateChainEditor (
703 _panel, _("Decrypting KDMs"), _border,
704 bind(&Config::set_decryption_chain, Config::instance(), _1),
705 bind(&Config::decryption_chain, Config::instance()),
706 bind(&KeysPage::nag_alter_decryption_chain, this)
713 KeysPage::signing_advanced ()
715 auto c = new CertificateChainEditor (
716 _panel, _("Signing DCPs and KDMs"), _border,
717 bind(&Config::set_signer_chain, Config::instance(), _1),
718 bind(&Config::signer_chain, Config::instance()),
726 KeysPage::export_decryption_chain_and_key ()
728 auto d = make_wx<wxFileDialog>(
729 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
730 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
733 if (d->ShowModal() != wxID_OK) {
737 boost::filesystem::path path(wx_to_std(d->GetPath()));
738 dcp::File f(path, "w");
740 throw OpenFileError(path, errno, OpenFileError::WRITE);
743 auto const chain = Config::instance()->decryption_chain()->chain();
744 f.checked_write(chain.c_str(), chain.length());
745 auto const key = Config::instance()->decryption_chain()->key();
746 DCPOMATIC_ASSERT(key);
747 f.checked_write(key->c_str(), key->length());
751 KeysPage::import_decryption_chain_and_key ()
753 if (NagDialog::maybe_nag (
755 Config::NAG_IMPORT_DECRYPTION_CHAIN,
756 _("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!"),
762 auto d = make_wx<wxFileDialog>(
763 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
766 if (d->ShowModal() != wxID_OK) {
770 auto new_chain = make_shared<dcp::CertificateChain>();
772 dcp::File f(wx_to_std(d->GetPath()), "r");
774 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
780 if (f.gets(buffer, 128) == 0) {
784 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
785 new_chain->add(dcp::Certificate(current));
787 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
788 new_chain->set_key(current);
793 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
794 Config::instance()->set_decryption_chain(new_chain);
796 error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s 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 = make_wx<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) {
833 boost::filesystem::path path(wx_to_std(d->GetPath()));
834 if (path.extension() != ".pem") {
837 dcp::File f(path, "w");
839 throw OpenFileError(path, errno, OpenFileError::WRITE);
842 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
843 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 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
875 _reset_to_default = new Button (_panel, _("Reset to default"));
876 table->Add (_reset_to_default, wxGBPosition(r, 1));
879 wxFont font = _sound_output_details->GetFont();
880 font.SetStyle (wxFONTSTYLE_ITALIC);
881 font.SetPointSize (font.GetPointSize() - 1);
882 _sound_output_details->SetFont (font);
884 RtAudio audio (DCPOMATIC_RTAUDIO_API);
885 #if (RTAUDIO_VERSION_MAJOR >= 6)
886 for (auto device_id: audio.getDeviceIds()) {
887 auto dev = audio.getDeviceInfo(device_id);
888 if (dev.outputChannels > 0) {
889 _sound_output->Append(std_to_wx(dev.name));
893 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
895 auto dev = audio.getDeviceInfo (i);
896 if (dev.probed && dev.outputChannels > 0) {
897 _sound_output->Append (std_to_wx (dev.name));
899 } catch (RtAudioError&) {
900 /* Something went wrong so let's just ignore that device */
905 _sound->bind(&SoundPage::sound_changed, this);
906 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
907 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
908 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
912 SoundPage::reset_to_default ()
914 Config::instance()->set_audio_mapping_to_default ();
918 SoundPage::map_changed (AudioMapping m)
920 Config::instance()->set_audio_mapping (m);
924 SoundPage::sound_changed ()
926 Config::instance()->set_sound (_sound->GetValue ());
930 SoundPage::sound_output_changed ()
932 RtAudio audio (DCPOMATIC_RTAUDIO_API);
933 auto const so = get_sound_output();
934 string default_device;
935 #if (RTAUDIO_VERSION_MAJOR >= 6)
936 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
939 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
940 } catch (RtAudioError&) {
944 if (!so || *so == default_device) {
945 Config::instance()->unset_sound_output ();
947 Config::instance()->set_sound_output (*so);
952 SoundPage::config_changed ()
954 auto config = Config::instance ();
956 checked_set (_sound, config->sound ());
958 auto const current_so = get_sound_output ();
959 optional<string> configured_so;
961 if (config->sound_output()) {
962 configured_so = config->sound_output().get();
964 /* No configured output means we should use the default */
965 RtAudio audio (DCPOMATIC_RTAUDIO_API);
966 #if (RTAUDIO_VERSION_MAJOR >= 6)
967 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
970 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
971 } catch (RtAudioError&) {
972 /* Probably no audio devices at all */
977 if (configured_so && current_so != configured_so) {
978 /* Update _sound_output with the configured value */
980 while (i < _sound_output->GetCount()) {
981 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
982 _sound_output->SetSelection (i);
989 RtAudio audio (DCPOMATIC_RTAUDIO_API);
991 map<int, wxString> apis;
992 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
993 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
994 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
995 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
996 apis[RtAudio::UNIX_JACK] = _("JACK");
997 apis[RtAudio::LINUX_ALSA] = _("ALSA");
998 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
999 apis[RtAudio::LINUX_OSS] = _("OSS");
1000 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1003 if (configured_so) {
1004 #if (RTAUDIO_VERSION_MAJOR >= 6)
1005 for (auto device_id: audio.getDeviceIds()) {
1006 auto info = audio.getDeviceInfo(device_id);
1007 if (info.name == *configured_so && info.outputChannels > 0) {
1008 channels = info.outputChannels;
1012 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1014 auto info = audio.getDeviceInfo(i);
1015 if (info.name == *configured_so && info.outputChannels > 0) {
1016 channels = info.outputChannels;
1018 } catch (RtAudioError&) {
1025 _sound_output_details->SetLabel (
1026 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1029 _map->set (Config::instance()->audio_mapping(channels));
1031 vector<NamedChannel> input;
1032 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1033 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1035 _map->set_input_channels (input);
1037 vector<NamedChannel> output;
1038 for (int i = 0; i < channels; ++i) {
1039 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1041 _map->set_output_channels (output);
1043 setup_sensitivity ();
1047 SoundPage::setup_sensitivity ()
1049 _sound_output->Enable (_sound->GetValue());
1052 /** @return Currently-selected preview sound output in the dialogue */
1054 SoundPage::get_sound_output ()
1056 int const sel = _sound_output->GetSelection ();
1057 if (sel == wxNOT_FOUND) {
1058 return optional<string> ();
1061 return wx_to_std (_sound_output->GetString (sel));
1065 LocationsPage::LocationsPage (wxSize panel_size, int border)
1066 : Page (panel_size, border)
1072 LocationsPage::GetName () const
1074 return _("Locations");
1077 #ifdef DCPOMATIC_OSX
1079 LocationsPage::GetLargeIcon () const
1081 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1086 LocationsPage::setup ()
1090 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1091 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1093 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1094 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1095 table->Add (_content_directory, wxGBPosition (r, 1));
1098 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1099 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1100 table->Add (_playlist_directory, wxGBPosition (r, 1));
1103 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1104 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1105 table->Add (_kdm_directory, wxGBPosition (r, 1));
1108 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1109 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1110 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1114 LocationsPage::config_changed ()
1116 auto config = Config::instance ();
1118 if (config->player_content_directory()) {
1119 checked_set (_content_directory, *config->player_content_directory());
1121 if (config->player_playlist_directory()) {
1122 checked_set (_playlist_directory, *config->player_playlist_directory());
1124 if (config->player_kdm_directory()) {
1125 checked_set (_kdm_directory, *config->player_kdm_directory());
1130 LocationsPage::content_directory_changed ()
1132 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1136 LocationsPage::playlist_directory_changed ()
1138 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1142 LocationsPage::kdm_directory_changed ()
1144 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));