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 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
142 checked_set (_language, languages);
143 table->Add (_language, wxGBPosition (r, 1));
146 auto restart = add_label_to_sizer (
147 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
149 wxFont font = restart->GetFont();
150 font.SetStyle (wxFONTSTYLE_ITALIC);
151 font.SetPointSize (font.GetPointSize() - 1);
152 restart->SetFont (font);
155 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
156 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
160 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
162 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
163 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
166 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
167 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
170 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
171 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
175 GeneralPage::config_changed ()
177 auto config = Config::instance ();
179 checked_set (_set_language, static_cast<bool>(config->language()));
181 /* Backwards compatibility of config file */
183 map<string, string> compat_map;
184 compat_map["fr"] = "fr_FR";
185 compat_map["it"] = "it_IT";
186 compat_map["es"] = "es_ES";
187 compat_map["sv"] = "sv_SE";
188 compat_map["de"] = "de_DE";
189 compat_map["nl"] = "nl_NL";
190 compat_map["ru"] = "ru_RU";
191 compat_map["pl"] = "pl_PL";
192 compat_map["da"] = "da_DK";
193 compat_map["pt"] = "pt_PT";
194 compat_map["sk"] = "sk_SK";
195 compat_map["cs"] = "cs_CZ";
196 compat_map["uk"] = "uk_UA";
198 auto lang = config->language().get_value_or("en_GB");
199 if (compat_map.find(lang) != compat_map.end ()) {
200 lang = compat_map[lang];
203 checked_set (_language, lang);
205 checked_set (_check_for_updates, config->check_for_updates ());
206 checked_set (_check_for_test_updates, config->check_for_test_updates ());
208 setup_sensitivity ();
212 GeneralPage::setup_sensitivity ()
214 _language->Enable (_set_language->GetValue ());
215 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
219 GeneralPage::set_language_changed ()
221 setup_sensitivity ();
222 if (_set_language->GetValue ()) {
225 Config::instance()->unset_language ();
230 GeneralPage::language_changed ()
232 int const sel = _language->GetSelection ();
234 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
236 Config::instance()->unset_language ();
241 GeneralPage::check_for_updates_changed ()
243 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
247 GeneralPage::check_for_test_updates_changed ()
249 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
252 CertificateChainEditor::CertificateChainEditor (
256 function<void (shared_ptr<dcp::CertificateChain>)> set,
257 function<shared_ptr<const dcp::CertificateChain> (void)> get,
258 function<bool (void)> nag_alter
260 : wxDialog (parent, wxID_ANY, title)
263 , _nag_alter (nag_alter)
265 _sizer = new wxBoxSizer (wxVERTICAL);
267 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
268 _sizer->Add (certificates_sizer, 0, wxALL, border);
270 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
275 ip.SetText (_("Type"));
277 _certificates->InsertColumn (0, ip);
283 ip.SetText (_("Thumbprint"));
286 wxFont font = ip.GetFont ();
287 font.SetFamily (wxFONTFAMILY_TELETYPE);
290 _certificates->InsertColumn (1, ip);
293 certificates_sizer->Add (_certificates, 1, wxEXPAND);
296 auto s = new wxBoxSizer (wxVERTICAL);
297 _add_certificate = new Button (this, _("Add..."));
298 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
299 _remove_certificate = new Button (this, _("Remove"));
300 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
301 _export_certificate = new Button (this, _("Export certificate..."));
302 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
303 _export_chain = new Button (this, _("Export chain..."));
304 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
305 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
308 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
309 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
312 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
313 _private_key = new StaticText (this, wxT(""));
314 wxFont font = _private_key->GetFont ();
315 font.SetFamily (wxFONTFAMILY_TELETYPE);
316 _private_key->SetFont (font);
317 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
318 _import_private_key = new Button (this, _("Import..."));
319 table->Add (_import_private_key, wxGBPosition (r, 2));
320 _export_private_key = new Button (this, _("Export..."));
321 table->Add (_export_private_key, wxGBPosition (r, 3));
324 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
325 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
326 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
327 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
330 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
331 font = *wxSMALL_FONT;
332 font.SetWeight (wxFONTWEIGHT_BOLD);
333 _private_key_bad->SetFont (font);
334 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
337 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
338 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
339 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
340 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
341 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
342 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
343 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
344 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
345 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
347 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
349 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
352 SetSizerAndFit (_sizer);
354 update_certificate_list ();
355 update_private_key ();
356 update_sensitivity ();
360 CertificateChainEditor::add_button (wxWindow* button)
362 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
367 CertificateChainEditor::add_certificate ()
369 auto d = new wxFileDialog (this, _("Select Certificate File"));
371 if (d->ShowModal() == wxID_OK) {
376 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
377 } catch (boost::filesystem::filesystem_error& e) {
378 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
383 if (!extra.empty ()) {
386 _("This file contains other certificates (or other data) after its first certificate. "
387 "Only the first certificate will be used.")
390 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
392 if (!chain->chain_valid ()) {
395 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
396 "Add certificates in order from root to intermediate to leaf.")
401 update_certificate_list ();
403 } catch (dcp::MiscError& e) {
404 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
410 update_sensitivity ();
414 CertificateChainEditor::remove_certificate ()
417 /* Cancel was clicked */
421 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
426 _certificates->DeleteItem (i);
427 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
431 update_sensitivity ();
432 update_certificate_list ();
436 CertificateChainEditor::export_certificate ()
438 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
443 auto all = _get()->root_to_leaf();
445 wxString default_name;
447 default_name = "root.pem";
448 } else if (i == static_cast<int>(all.size() - 1)) {
449 default_name = "leaf.pem";
451 default_name = "intermediate.pem";
454 auto d = new wxFileDialog(
455 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
456 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
459 auto j = all.begin ();
460 for (int k = 0; k < i; ++k) {
464 if (d->ShowModal () == wxID_OK) {
465 boost::filesystem::path path (wx_to_std(d->GetPath()));
466 if (path.extension() != ".pem") {
469 dcp::File f(path, "w");
471 throw OpenFileError (path, errno, OpenFileError::WRITE);
474 string const s = j->certificate (true);
475 f.checked_write(s.c_str(), s.length());
481 CertificateChainEditor::export_chain ()
483 auto d = new wxFileDialog (
484 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
485 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
488 if (d->ShowModal () == wxID_OK) {
489 boost::filesystem::path path (wx_to_std(d->GetPath()));
490 if (path.extension() != ".pem") {
493 dcp::File f(path, "w");
495 throw OpenFileError (path, errno, OpenFileError::WRITE);
498 auto const s = _get()->chain();
499 f.checked_write (s.c_str(), s.length());
506 CertificateChainEditor::update_certificate_list ()
508 _certificates->DeleteAllItems ();
510 auto certs = _get()->root_to_leaf();
511 for (auto const& i: certs) {
514 _certificates->InsertItem (item);
515 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
518 _certificates->SetItem (n, 0, _("Root"));
519 } else if (n == (certs.size() - 1)) {
520 _certificates->SetItem (n, 0, _("Leaf"));
522 _certificates->SetItem (n, 0, _("Intermediate"));
528 static wxColour normal = _private_key_bad->GetForegroundColour ();
530 if (_get()->private_key_valid()) {
531 _private_key_bad->Hide ();
532 _private_key_bad->SetForegroundColour (normal);
534 _private_key_bad->Show ();
535 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
540 CertificateChainEditor::remake_certificates ()
543 /* Cancel was clicked */
547 auto d = new MakeChainDialog (this, _get());
549 if (d->ShowModal () == wxID_OK) {
551 update_certificate_list ();
552 update_private_key ();
559 CertificateChainEditor::update_sensitivity ()
561 /* We can only remove the leaf certificate */
562 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
563 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
567 CertificateChainEditor::update_private_key ()
569 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
574 CertificateChainEditor::import_private_key ()
576 auto d = new wxFileDialog (this, _("Select Key File"));
578 if (d->ShowModal() == wxID_OK) {
580 boost::filesystem::path p (wx_to_std (d->GetPath ()));
581 if (boost::filesystem::file_size (p) > 8192) {
584 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
589 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
590 chain->set_key (dcp::file_to_string (p));
592 update_private_key ();
593 } catch (std::exception& e) {
594 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
616 if (d->ShowModal () == wxID_OK) {
617 boost::filesystem::path path (wx_to_std(d->GetPath()));
618 if (path.extension() != ".pem") {
621 dcp::File f(path, "w");
623 throw OpenFileError (path, errno, OpenFileError::WRITE);
626 auto const s = _get()->key().get ();
627 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
735 if (d->ShowModal () == wxID_OK) {
736 boost::filesystem::path path (wx_to_std(d->GetPath()));
737 dcp::File f(path, "w");
739 throw OpenFileError (path, errno, OpenFileError::WRITE);
742 auto const chain = Config::instance()->decryption_chain()->chain();
743 f.checked_write (chain.c_str(), chain.length());
744 auto const key = Config::instance()->decryption_chain()->key();
745 DCPOMATIC_ASSERT (key);
746 f.checked_write(key->c_str(), key->length());
753 KeysPage::import_decryption_chain_and_key ()
755 if (NagDialog::maybe_nag (
757 Config::NAG_IMPORT_DECRYPTION_CHAIN,
758 _("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!"),
764 auto d = new wxFileDialog (
765 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
768 if (d->ShowModal () == wxID_OK) {
769 auto new_chain = make_shared<dcp::CertificateChain>();
771 dcp::File f(wx_to_std(d->GetPath()), "r");
773 throw OpenFileError (f.path(), errno, OpenFileError::WRITE);
779 if (f.gets(buffer, 128) == 0) {
783 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
784 new_chain->add (dcp::Certificate (current));
786 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
787 new_chain->set_key (current);
792 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
793 Config::instance()->set_decryption_chain (new_chain);
795 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
802 KeysPage::nag_alter_decryption_chain ()
804 return NagDialog::maybe_nag (
806 Config::NAG_ALTER_DECRYPTION_CHAIN,
807 _("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!"),
813 KeysPage::export_decryption_certificate ()
815 auto config = Config::instance();
816 wxString default_name = "dcpomatic";
817 if (!config->dcp_creator().empty()) {
818 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
820 if (!config->dcp_issuer().empty()) {
821 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
823 default_name += wxT("_kdm_decryption_cert.pem");
825 auto d = new wxFileDialog (
826 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
827 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
830 if (d->ShowModal () == wxID_OK) {
831 boost::filesystem::path path (wx_to_std(d->GetPath()));
832 if (path.extension() != ".pem") {
835 dcp::File f(path, "w");
837 throw OpenFileError (path, errno, OpenFileError::WRITE);
840 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
841 f.checked_write(s.c_str(), s.length());
848 SoundPage::GetName () const
856 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
857 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
861 _sound = new CheckBox (_panel, _("Play sound via"));
862 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
863 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
864 _sound_output = new wxChoice (_panel, wxID_ANY);
865 s->Add (_sound_output, 0);
866 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
867 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
868 table->Add (s, wxGBPosition(r, 1));
871 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
872 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
873 _map->SetSize (-1, 400);
874 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
877 _reset_to_default = new Button (_panel, _("Reset to default"));
878 table->Add (_reset_to_default, wxGBPosition(r, 1));
881 wxFont font = _sound_output_details->GetFont();
882 font.SetStyle (wxFONTSTYLE_ITALIC);
883 font.SetPointSize (font.GetPointSize() - 1);
884 _sound_output_details->SetFont (font);
886 RtAudio audio (DCPOMATIC_RTAUDIO_API);
887 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
889 auto dev = audio.getDeviceInfo (i);
890 if (dev.probed && dev.outputChannels > 0) {
891 _sound_output->Append (std_to_wx (dev.name));
893 } catch (RtAudioError&) {
894 /* Something went wrong so let's just ignore that device */
898 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
899 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
900 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
901 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
905 SoundPage::reset_to_default ()
907 Config::instance()->set_audio_mapping_to_default ();
911 SoundPage::map_changed (AudioMapping m)
913 Config::instance()->set_audio_mapping (m);
917 SoundPage::sound_changed ()
919 Config::instance()->set_sound (_sound->GetValue ());
923 SoundPage::sound_output_changed ()
925 RtAudio audio (DCPOMATIC_RTAUDIO_API);
926 auto const so = get_sound_output();
927 string default_device;
929 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
930 } catch (RtAudioError&) {
933 if (!so || *so == default_device) {
934 Config::instance()->unset_sound_output ();
936 Config::instance()->set_sound_output (*so);
941 SoundPage::config_changed ()
943 auto config = Config::instance ();
945 checked_set (_sound, config->sound ());
947 auto const current_so = get_sound_output ();
948 optional<string> configured_so;
950 if (config->sound_output()) {
951 configured_so = config->sound_output().get();
953 /* No configured output means we should use the default */
954 RtAudio audio (DCPOMATIC_RTAUDIO_API);
956 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
957 } catch (RtAudioError&) {
958 /* Probably no audio devices at all */
962 if (configured_so && current_so != configured_so) {
963 /* Update _sound_output with the configured value */
965 while (i < _sound_output->GetCount()) {
966 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
967 _sound_output->SetSelection (i);
974 RtAudio audio (DCPOMATIC_RTAUDIO_API);
976 map<int, wxString> apis;
977 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
978 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
979 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
980 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
981 apis[RtAudio::UNIX_JACK] = _("JACK");
982 apis[RtAudio::LINUX_ALSA] = _("ALSA");
983 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
984 apis[RtAudio::LINUX_OSS] = _("OSS");
985 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
989 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
991 auto info = audio.getDeviceInfo(i);
992 if (info.name == *configured_so && info.outputChannels > 0) {
993 channels = info.outputChannels;
995 } catch (RtAudioError&) {
1001 _sound_output_details->SetLabel (
1002 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1005 _map->set (Config::instance()->audio_mapping(channels));
1007 vector<NamedChannel> input;
1008 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1009 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1011 _map->set_input_channels (input);
1013 vector<NamedChannel> output;
1014 for (int i = 0; i < channels; ++i) {
1015 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1017 _map->set_output_channels (output);
1019 setup_sensitivity ();
1023 SoundPage::setup_sensitivity ()
1025 _sound_output->Enable (_sound->GetValue());
1028 /** @return Currently-selected preview sound output in the dialogue */
1030 SoundPage::get_sound_output ()
1032 int const sel = _sound_output->GetSelection ();
1033 if (sel == wxNOT_FOUND) {
1034 return optional<string> ();
1037 return wx_to_std (_sound_output->GetString (sel));
1041 LocationsPage::LocationsPage (wxSize panel_size, int border)
1042 : Page (panel_size, border)
1048 LocationsPage::GetName () const
1050 return _("Locations");
1053 #ifdef DCPOMATIC_OSX
1055 LocationsPage::GetLargeIcon () const
1057 return wxBitmap(bitmap_path("locations.png"), wxBITMAP_TYPE_PNG);
1062 LocationsPage::setup ()
1066 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1067 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1069 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1070 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1071 table->Add (_content_directory, wxGBPosition (r, 1));
1074 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1075 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1076 table->Add (_playlist_directory, wxGBPosition (r, 1));
1079 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1080 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1081 table->Add (_kdm_directory, wxGBPosition (r, 1));
1084 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1085 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1086 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1090 LocationsPage::config_changed ()
1092 auto config = Config::instance ();
1094 if (config->player_content_directory()) {
1095 checked_set (_content_directory, *config->player_content_directory());
1097 if (config->player_playlist_directory()) {
1098 checked_set (_playlist_directory, *config->player_playlist_directory());
1100 if (config->player_kdm_directory()) {
1101 checked_set (_kdm_directory, *config->player_kdm_directory());
1106 LocationsPage::content_directory_changed ()
1108 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1112 LocationsPage::playlist_directory_changed ()
1114 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1118 LocationsPage::kdm_directory_changed ()
1120 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));