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 "lib/constants.h"
30 #include <dcp/raw_convert.h>
35 using std::make_shared;
38 using std::shared_ptr;
42 using boost::optional;
43 #if BOOST_VERSION >= 106100
44 using namespace boost::placeholders;
55 Page::Page (wxSize panel_size, int border)
58 , _panel_size (panel_size)
59 , _window_exists (false)
61 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
66 Page::CreateWindow (wxWindow* parent)
68 return create_window (parent);
73 Page::create_window (wxWindow* parent)
75 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
76 auto s = new wxBoxSizer (wxVERTICAL);
80 _window_exists = true;
83 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
89 Page::config_changed_wrapper ()
97 Page::window_destroyed ()
99 _window_exists = false;
103 GeneralPage::GeneralPage (wxSize panel_size, int border)
104 : Page (panel_size, border)
111 GeneralPage::GetName () const
118 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
120 _set_language = new CheckBox (_panel, _("Set language"));
121 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
122 _language = new wxChoice (_panel, wxID_ANY);
123 vector<pair<string, string>> languages;
124 languages.push_back (make_pair("Čeština", "cs_CZ"));
125 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
126 languages.push_back (make_pair("Dansk", "da_DK"));
127 languages.push_back (make_pair("Deutsch", "de_DE"));
128 languages.push_back (make_pair("English", "en_GB"));
129 languages.push_back (make_pair("Español", "es_ES"));
130 languages.push_back (make_pair("Français", "fr_FR"));
131 languages.push_back (make_pair("Italiano", "it_IT"));
132 languages.push_back (make_pair("Nederlands", "nl_NL"));
133 languages.push_back (make_pair("Русский", "ru_RU"));
134 languages.push_back (make_pair("Polski", "pl_PL"));
135 languages.push_back (make_pair("Português europeu", "pt_PT"));
136 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
137 languages.push_back (make_pair("Svenska", "sv_SE"));
138 languages.push_back (make_pair("Slovenščina", "sl_SI"));
139 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
140 // languages.push_back (make_pair("Türkçe", "tr_TR"));
141 languages.push_back (make_pair("українська мова", "uk_UA"));
142 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
143 checked_set (_language, languages);
144 table->Add (_language, wxGBPosition (r, 1));
147 auto restart = add_label_to_sizer (
148 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
150 wxFont font = restart->GetFont();
151 font.SetStyle (wxFONTSTYLE_ITALIC);
152 font.SetPointSize (font.GetPointSize() - 1);
153 restart->SetFont (font);
156 _set_language->bind(&GeneralPage::set_language_changed, this);
157 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
161 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
163 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
164 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
167 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
168 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
171 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
172 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
176 GeneralPage::config_changed ()
178 auto config = Config::instance ();
180 checked_set (_set_language, static_cast<bool>(config->language()));
182 /* Backwards compatibility of config file */
184 map<string, string> compat_map;
185 compat_map["fr"] = "fr_FR";
186 compat_map["it"] = "it_IT";
187 compat_map["es"] = "es_ES";
188 compat_map["sv"] = "sv_SE";
189 compat_map["de"] = "de_DE";
190 compat_map["nl"] = "nl_NL";
191 compat_map["ru"] = "ru_RU";
192 compat_map["pl"] = "pl_PL";
193 compat_map["da"] = "da_DK";
194 compat_map["pt"] = "pt_PT";
195 compat_map["sk"] = "sk_SK";
196 compat_map["cs"] = "cs_CZ";
197 compat_map["uk"] = "uk_UA";
199 auto lang = config->language().get_value_or("en_GB");
200 if (compat_map.find(lang) != compat_map.end ()) {
201 lang = compat_map[lang];
204 checked_set (_language, lang);
206 checked_set (_check_for_updates, config->check_for_updates ());
207 checked_set (_check_for_test_updates, config->check_for_test_updates ());
209 setup_sensitivity ();
213 GeneralPage::setup_sensitivity ()
215 _language->Enable (_set_language->GetValue ());
216 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
220 GeneralPage::set_language_changed ()
222 setup_sensitivity ();
223 if (_set_language->GetValue ()) {
226 Config::instance()->unset_language ();
231 GeneralPage::language_changed ()
233 int const sel = _language->GetSelection ();
235 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
237 Config::instance()->unset_language ();
242 GeneralPage::check_for_updates_changed ()
244 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
248 GeneralPage::check_for_test_updates_changed ()
250 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
253 CertificateChainEditor::CertificateChainEditor (
257 function<void (shared_ptr<dcp::CertificateChain>)> set,
258 function<shared_ptr<const dcp::CertificateChain> (void)> get,
259 function<bool (void)> nag_alter
261 : wxDialog (parent, wxID_ANY, title)
264 , _nag_alter (nag_alter)
266 _sizer = new wxBoxSizer (wxVERTICAL);
268 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
269 _sizer->Add (certificates_sizer, 0, wxALL, border);
271 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
276 ip.SetText (_("Type"));
278 _certificates->InsertColumn (0, ip);
284 ip.SetText (_("Thumbprint"));
287 wxFont font = ip.GetFont ();
288 font.SetFamily (wxFONTFAMILY_TELETYPE);
291 _certificates->InsertColumn (1, ip);
294 certificates_sizer->Add (_certificates, 1, wxEXPAND);
297 auto s = new wxBoxSizer (wxVERTICAL);
298 _add_certificate = new Button (this, _("Add..."));
299 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 _remove_certificate = new Button (this, _("Remove"));
301 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
302 _export_certificate = new Button (this, _("Export certificate..."));
303 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304 _export_chain = new Button (this, _("Export chain..."));
305 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
306 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
309 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
310 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
313 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
314 _private_key = new StaticText (this, wxT(""));
315 wxFont font = _private_key->GetFont ();
316 font.SetFamily (wxFONTFAMILY_TELETYPE);
317 _private_key->SetFont (font);
318 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
319 _import_private_key = new Button (this, _("Import..."));
320 table->Add (_import_private_key, wxGBPosition (r, 2));
321 _export_private_key = new Button (this, _("Export..."));
322 table->Add (_export_private_key, wxGBPosition (r, 3));
325 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
326 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
327 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
328 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
331 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
332 font = *wxSMALL_FONT;
333 font.SetWeight (wxFONTWEIGHT_BOLD);
334 _private_key_bad->SetFont (font);
335 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
338 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
339 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
340 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
341 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
342 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
343 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
344 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
345 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
346 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
348 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
350 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
353 SetSizerAndFit (_sizer);
355 update_certificate_list ();
356 update_private_key ();
357 update_sensitivity ();
361 CertificateChainEditor::add_button (wxWindow* button)
363 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
368 CertificateChainEditor::add_certificate ()
370 auto d = new wxFileDialog (this, _("Select Certificate File"));
371 ScopeGuard sg = [d]() { d->Destroy(); };
373 if (d->ShowModal() == wxID_OK) {
378 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
379 } catch (boost::filesystem::filesystem_error& e) {
380 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
384 if (!extra.empty ()) {
387 _("This file contains other certificates (or other data) after its first certificate. "
388 "Only the first certificate will be used.")
391 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
393 if (!chain->chain_valid ()) {
396 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
397 "Add certificates in order from root to intermediate to leaf.")
402 update_certificate_list ();
404 } catch (dcp::MiscError& e) {
405 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
457 ScopeGuard sg = [d]() { d->Destroy(); };
459 auto j = all.begin ();
460 for (int k = 0; k < i; ++k) {
464 if (d->ShowModal() != wxID_OK) {
468 boost::filesystem::path path(wx_to_std(d->GetPath()));
469 if (path.extension() != ".pem") {
472 dcp::File f(path, "w");
474 throw OpenFileError(path, errno, OpenFileError::WRITE);
477 string const s = j->certificate(true);
478 f.checked_write(s.c_str(), s.length());
482 CertificateChainEditor::export_chain ()
484 auto d = new wxFileDialog (
485 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
486 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
488 ScopeGuard sg = [d]() { d->Destroy(); };
490 if (d->ShowModal() != wxID_OK) {
494 boost::filesystem::path path(wx_to_std(d->GetPath()));
495 if (path.extension() != ".pem") {
498 dcp::File f(path, "w");
500 throw OpenFileError(path, errno, OpenFileError::WRITE);
503 auto const s = _get()->chain();
504 f.checked_write(s.c_str(), s.length());
508 CertificateChainEditor::update_certificate_list ()
510 _certificates->DeleteAllItems ();
512 auto certs = _get()->root_to_leaf();
513 for (auto const& i: certs) {
516 _certificates->InsertItem (item);
517 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
520 _certificates->SetItem (n, 0, _("Root"));
521 } else if (n == (certs.size() - 1)) {
522 _certificates->SetItem (n, 0, _("Leaf"));
524 _certificates->SetItem (n, 0, _("Intermediate"));
530 static wxColour normal = _private_key_bad->GetForegroundColour ();
532 if (_get()->private_key_valid()) {
533 _private_key_bad->Hide ();
534 _private_key_bad->SetForegroundColour (normal);
536 _private_key_bad->Show ();
537 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
542 CertificateChainEditor::remake_certificates ()
545 /* Cancel was clicked */
549 auto d = new MakeChainDialog (this, _get());
550 ScopeGuard sg = [d]() { d->Destroy(); };
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 = new wxFileDialog (this, _("Select Key File"));
578 ScopeGuard sg = [d]() { d->Destroy(); };
580 if (d->ShowModal() == wxID_OK) {
582 boost::filesystem::path p (wx_to_std (d->GetPath ()));
583 if (boost::filesystem::file_size (p) > 8192) {
586 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
591 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
592 chain->set_key (dcp::file_to_string (p));
594 update_private_key ();
595 } catch (std::exception& e) {
596 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
600 update_sensitivity ();
604 CertificateChainEditor::export_private_key ()
606 auto key = _get()->key();
611 auto d = new wxFileDialog (
612 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
613 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
615 ScopeGuard sg = [d]() { d->Destroy(); };
617 if (d->ShowModal () == wxID_OK) {
618 boost::filesystem::path path (wx_to_std(d->GetPath()));
619 if (path.extension() != ".pem") {
622 dcp::File f(path, "w");
624 throw OpenFileError (path, errno, OpenFileError::WRITE);
627 auto const s = _get()->key().get ();
628 f.checked_write(s.c_str(), s.length());
633 KeysPage::GetName () const
641 wxFont subheading_font (*wxNORMAL_FONT);
642 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
644 auto sizer = _panel->GetSizer();
647 auto m = new StaticText (_panel, _("Decrypting KDMs"));
648 m->SetFont (subheading_font);
649 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
652 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
654 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
655 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
657 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
658 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
659 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
660 auto decryption_advanced = new Button (_panel, _("Advanced..."));
661 kdm_buttons->Add (decryption_advanced, 0);
663 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
665 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
666 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
667 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
668 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
671 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
672 m->SetFont (subheading_font);
673 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
676 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
678 auto signing_advanced = new Button (_panel, _("Advanced..."));
679 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
680 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
681 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
683 sizer->Add (signing_buttons, 0, wxLEFT, _border);
685 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
686 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
691 KeysPage::remake_signing ()
693 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
695 if (d->ShowModal () == wxID_OK) {
696 Config::instance()->set_signer_chain(d->get());
702 KeysPage::decryption_advanced ()
704 auto c = new CertificateChainEditor (
705 _panel, _("Decrypting KDMs"), _border,
706 bind(&Config::set_decryption_chain, Config::instance(), _1),
707 bind(&Config::decryption_chain, Config::instance()),
708 bind(&KeysPage::nag_alter_decryption_chain, this)
715 KeysPage::signing_advanced ()
717 auto c = new CertificateChainEditor (
718 _panel, _("Signing DCPs and KDMs"), _border,
719 bind(&Config::set_signer_chain, Config::instance(), _1),
720 bind(&Config::signer_chain, Config::instance()),
728 KeysPage::export_decryption_chain_and_key ()
730 auto d = new wxFileDialog (
731 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
732 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
734 ScopeGuard sg = [d]() { d->Destroy(); };
736 if (d->ShowModal() != wxID_OK) {
740 boost::filesystem::path path(wx_to_std(d->GetPath()));
741 dcp::File f(path, "w");
743 throw OpenFileError(path, errno, OpenFileError::WRITE);
746 auto const chain = Config::instance()->decryption_chain()->chain();
747 f.checked_write(chain.c_str(), chain.length());
748 auto const key = Config::instance()->decryption_chain()->key();
749 DCPOMATIC_ASSERT(key);
750 f.checked_write(key->c_str(), key->length());
754 KeysPage::import_decryption_chain_and_key ()
756 if (NagDialog::maybe_nag (
758 Config::NAG_IMPORT_DECRYPTION_CHAIN,
759 _("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!"),
765 auto d = new wxFileDialog (
766 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
768 ScopeGuard sg = [d]() { d->Destroy(); };
770 if (d->ShowModal() != wxID_OK) {
774 auto new_chain = make_shared<dcp::CertificateChain>();
776 dcp::File f(wx_to_std(d->GetPath()), "r");
778 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
784 if (f.gets(buffer, 128) == 0) {
788 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
789 new_chain->add(dcp::Certificate(current));
791 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
792 new_chain->set_key(current);
797 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
798 Config::instance()->set_decryption_chain(new_chain);
800 error_dialog(_panel, _("Invalid DCP-o-matic export file"));
805 KeysPage::nag_alter_decryption_chain ()
807 return NagDialog::maybe_nag (
809 Config::NAG_ALTER_DECRYPTION_CHAIN,
810 _("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!"),
816 KeysPage::export_decryption_certificate ()
818 auto config = Config::instance();
819 wxString default_name = "dcpomatic";
820 if (!config->dcp_creator().empty()) {
821 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
823 if (!config->dcp_issuer().empty()) {
824 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
826 default_name += wxT("_kdm_decryption_cert.pem");
828 auto d = new wxFileDialog (
829 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
830 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
832 ScopeGuard sg = [d]() { d->Destroy(); };
834 if (d->ShowModal() != wxID_OK) {
838 boost::filesystem::path path(wx_to_std(d->GetPath()));
839 if (path.extension() != ".pem") {
842 dcp::File f(path, "w");
844 throw OpenFileError(path, errno, OpenFileError::WRITE);
847 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
848 f.checked_write(s.c_str(), s.length());
852 SoundPage::GetName () const
860 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
861 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
865 _sound = new CheckBox (_panel, _("Play sound via"));
866 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
867 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
868 _sound_output = new wxChoice (_panel, wxID_ANY);
869 s->Add (_sound_output, 0);
870 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
871 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
872 table->Add (s, wxGBPosition(r, 1));
875 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
876 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
877 _map->SetSize (-1, 400);
878 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
881 _reset_to_default = new Button (_panel, _("Reset to default"));
882 table->Add (_reset_to_default, wxGBPosition(r, 1));
885 wxFont font = _sound_output_details->GetFont();
886 font.SetStyle (wxFONTSTYLE_ITALIC);
887 font.SetPointSize (font.GetPointSize() - 1);
888 _sound_output_details->SetFont (font);
890 RtAudio audio (DCPOMATIC_RTAUDIO_API);
891 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
893 auto dev = audio.getDeviceInfo (i);
894 if (dev.probed && dev.outputChannels > 0) {
895 _sound_output->Append (std_to_wx (dev.name));
897 } catch (RtAudioError&) {
898 /* Something went wrong so let's just ignore that device */
902 _sound->bind(&SoundPage::sound_changed, this);
903 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
904 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
905 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
909 SoundPage::reset_to_default ()
911 Config::instance()->set_audio_mapping_to_default ();
915 SoundPage::map_changed (AudioMapping m)
917 Config::instance()->set_audio_mapping (m);
921 SoundPage::sound_changed ()
923 Config::instance()->set_sound (_sound->GetValue ());
927 SoundPage::sound_output_changed ()
929 RtAudio audio (DCPOMATIC_RTAUDIO_API);
930 auto const so = get_sound_output();
931 string default_device;
933 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
934 } catch (RtAudioError&) {
937 if (!so || *so == default_device) {
938 Config::instance()->unset_sound_output ();
940 Config::instance()->set_sound_output (*so);
945 SoundPage::config_changed ()
947 auto config = Config::instance ();
949 checked_set (_sound, config->sound ());
951 auto const current_so = get_sound_output ();
952 optional<string> configured_so;
954 if (config->sound_output()) {
955 configured_so = config->sound_output().get();
957 /* No configured output means we should use the default */
958 RtAudio audio (DCPOMATIC_RTAUDIO_API);
960 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
961 } catch (RtAudioError&) {
962 /* Probably no audio devices at all */
966 if (configured_so && current_so != configured_so) {
967 /* Update _sound_output with the configured value */
969 while (i < _sound_output->GetCount()) {
970 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
971 _sound_output->SetSelection (i);
978 RtAudio audio (DCPOMATIC_RTAUDIO_API);
980 map<int, wxString> apis;
981 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
982 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
983 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
984 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
985 apis[RtAudio::UNIX_JACK] = _("JACK");
986 apis[RtAudio::LINUX_ALSA] = _("ALSA");
987 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
988 apis[RtAudio::LINUX_OSS] = _("OSS");
989 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
993 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
995 auto info = audio.getDeviceInfo(i);
996 if (info.name == *configured_so && info.outputChannels > 0) {
997 channels = info.outputChannels;
999 } catch (RtAudioError&) {
1005 _sound_output_details->SetLabel (
1006 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1009 _map->set (Config::instance()->audio_mapping(channels));
1011 vector<NamedChannel> input;
1012 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1013 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1015 _map->set_input_channels (input);
1017 vector<NamedChannel> output;
1018 for (int i = 0; i < channels; ++i) {
1019 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1021 _map->set_output_channels (output);
1023 setup_sensitivity ();
1027 SoundPage::setup_sensitivity ()
1029 _sound_output->Enable (_sound->GetValue());
1032 /** @return Currently-selected preview sound output in the dialogue */
1034 SoundPage::get_sound_output ()
1036 int const sel = _sound_output->GetSelection ();
1037 if (sel == wxNOT_FOUND) {
1038 return optional<string> ();
1041 return wx_to_std (_sound_output->GetString (sel));
1045 LocationsPage::LocationsPage (wxSize panel_size, int border)
1046 : Page (panel_size, border)
1052 LocationsPage::GetName () const
1054 return _("Locations");
1057 #ifdef DCPOMATIC_OSX
1059 LocationsPage::GetLargeIcon () const
1061 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1066 LocationsPage::setup ()
1070 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1071 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1073 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1074 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1075 table->Add (_content_directory, wxGBPosition (r, 1));
1078 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1079 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1080 table->Add (_playlist_directory, wxGBPosition (r, 1));
1083 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1084 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1085 table->Add (_kdm_directory, wxGBPosition (r, 1));
1088 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1089 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1090 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1094 LocationsPage::config_changed ()
1096 auto config = Config::instance ();
1098 if (config->player_content_directory()) {
1099 checked_set (_content_directory, *config->player_content_directory());
1101 if (config->player_playlist_directory()) {
1102 checked_set (_playlist_directory, *config->player_playlist_directory());
1104 if (config->player_kdm_directory()) {
1105 checked_set (_kdm_directory, *config->player_kdm_directory());
1110 LocationsPage::content_directory_changed ()
1112 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1116 LocationsPage::playlist_directory_changed ()
1118 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1122 LocationsPage::kdm_directory_changed ()
1124 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));