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 "lib/constants.h"
31 #include <dcp/raw_convert.h>
36 using std::make_shared;
39 using std::shared_ptr;
43 using boost::optional;
44 #if BOOST_VERSION >= 106100
45 using namespace boost::placeholders;
56 Page::Page (wxSize panel_size, int border)
59 , _panel_size (panel_size)
60 , _window_exists (false)
62 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
67 Page::CreateWindow (wxWindow* parent)
69 return create_window (parent);
74 Page::create_window (wxWindow* parent)
76 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
77 auto s = new wxBoxSizer (wxVERTICAL);
81 _window_exists = true;
84 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
90 Page::config_changed_wrapper ()
98 Page::window_destroyed ()
100 _window_exists = false;
104 GeneralPage::GeneralPage (wxSize panel_size, int border)
105 : Page (panel_size, border)
112 GeneralPage::GetName () const
119 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
121 _set_language = new CheckBox (_panel, _("Set language"));
122 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
123 _language = new wxChoice (_panel, wxID_ANY);
124 vector<pair<string, string>> languages;
125 languages.push_back (make_pair("Čeština", "cs_CZ"));
126 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
127 languages.push_back (make_pair("Dansk", "da_DK"));
128 languages.push_back (make_pair("Deutsch", "de_DE"));
129 languages.push_back (make_pair("English", "en_GB"));
130 languages.push_back (make_pair("Español", "es_ES"));
131 languages.push_back (make_pair("Français", "fr_FR"));
132 languages.push_back (make_pair("Italiano", "it_IT"));
133 languages.push_back (make_pair("Nederlands", "nl_NL"));
134 languages.push_back (make_pair("Русский", "ru_RU"));
135 languages.push_back (make_pair("Polski", "pl_PL"));
136 languages.push_back (make_pair("Português europeu", "pt_PT"));
137 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
138 languages.push_back (make_pair("Svenska", "sv_SE"));
139 languages.push_back (make_pair("Slovenščina", "sl_SI"));
140 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
141 // languages.push_back (make_pair("Türkçe", "tr_TR"));
142 languages.push_back (make_pair("українська мова", "uk_UA"));
143 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
144 checked_set (_language, languages);
145 table->Add (_language, wxGBPosition (r, 1));
148 auto restart = add_label_to_sizer (
149 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
151 wxFont font = restart->GetFont();
152 font.SetStyle (wxFONTSTYLE_ITALIC);
153 font.SetPointSize (font.GetPointSize() - 1);
154 restart->SetFont (font);
157 _set_language->bind(&GeneralPage::set_language_changed, this);
158 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
162 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
164 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
165 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
168 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
169 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
172 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
173 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
177 GeneralPage::config_changed ()
179 auto config = Config::instance ();
181 checked_set (_set_language, static_cast<bool>(config->language()));
183 /* Backwards compatibility of config file */
185 map<string, string> compat_map;
186 compat_map["fr"] = "fr_FR";
187 compat_map["it"] = "it_IT";
188 compat_map["es"] = "es_ES";
189 compat_map["sv"] = "sv_SE";
190 compat_map["de"] = "de_DE";
191 compat_map["nl"] = "nl_NL";
192 compat_map["ru"] = "ru_RU";
193 compat_map["pl"] = "pl_PL";
194 compat_map["da"] = "da_DK";
195 compat_map["pt"] = "pt_PT";
196 compat_map["sk"] = "sk_SK";
197 compat_map["cs"] = "cs_CZ";
198 compat_map["uk"] = "uk_UA";
200 auto lang = config->language().get_value_or("en_GB");
201 if (compat_map.find(lang) != compat_map.end ()) {
202 lang = compat_map[lang];
205 checked_set (_language, lang);
207 checked_set (_check_for_updates, config->check_for_updates ());
208 checked_set (_check_for_test_updates, config->check_for_test_updates ());
210 setup_sensitivity ();
214 GeneralPage::setup_sensitivity ()
216 _language->Enable (_set_language->GetValue ());
217 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
221 GeneralPage::set_language_changed ()
223 setup_sensitivity ();
224 if (_set_language->GetValue ()) {
227 Config::instance()->unset_language ();
232 GeneralPage::language_changed ()
234 int const sel = _language->GetSelection ();
236 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
238 Config::instance()->unset_language ();
243 GeneralPage::check_for_updates_changed ()
245 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
249 GeneralPage::check_for_test_updates_changed ()
251 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
254 CertificateChainEditor::CertificateChainEditor (
258 function<void (shared_ptr<dcp::CertificateChain>)> set,
259 function<shared_ptr<const dcp::CertificateChain> (void)> get,
260 function<bool (void)> nag_alter
262 : wxDialog (parent, wxID_ANY, title)
265 , _nag_alter (nag_alter)
267 _sizer = new wxBoxSizer (wxVERTICAL);
269 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
270 _sizer->Add (certificates_sizer, 0, wxALL, border);
272 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
277 ip.SetText (_("Type"));
279 _certificates->InsertColumn (0, ip);
285 ip.SetText (_("Thumbprint"));
288 wxFont font = ip.GetFont ();
289 font.SetFamily (wxFONTFAMILY_TELETYPE);
292 _certificates->InsertColumn (1, ip);
295 certificates_sizer->Add (_certificates, 1, wxEXPAND);
298 auto s = new wxBoxSizer (wxVERTICAL);
299 _add_certificate = new Button (this, _("Add..."));
300 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
301 _remove_certificate = new Button (this, _("Remove"));
302 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
303 _export_certificate = new Button (this, _("Export certificate..."));
304 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
305 _export_chain = new Button (this, _("Export chain..."));
306 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
307 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
310 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
311 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
314 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
315 _private_key = new StaticText (this, wxT(""));
316 wxFont font = _private_key->GetFont ();
317 font.SetFamily (wxFONTFAMILY_TELETYPE);
318 _private_key->SetFont (font);
319 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
320 _import_private_key = new Button (this, _("Import..."));
321 table->Add (_import_private_key, wxGBPosition (r, 2));
322 _export_private_key = new Button (this, _("Export..."));
323 table->Add (_export_private_key, wxGBPosition (r, 3));
326 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
327 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
328 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
329 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
332 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
333 font = *wxSMALL_FONT;
334 font.SetWeight (wxFONTWEIGHT_BOLD);
335 _private_key_bad->SetFont (font);
336 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
339 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
340 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
341 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
342 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
343 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
344 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
345 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
346 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
347 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
349 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
351 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
354 SetSizerAndFit (_sizer);
356 update_certificate_list ();
357 update_private_key ();
358 update_sensitivity ();
362 CertificateChainEditor::add_button (wxWindow* button)
364 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
369 CertificateChainEditor::add_certificate ()
371 auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
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 = make_wx<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) {
467 boost::filesystem::path path(wx_to_std(d->GetPath()));
468 if (path.extension() != ".pem") {
471 dcp::File f(path, "w");
473 throw OpenFileError(path, errno, OpenFileError::WRITE);
476 string const s = j->certificate(true);
477 f.checked_write(s.c_str(), s.length());
481 CertificateChainEditor::export_chain ()
483 auto d = make_wx<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) {
492 boost::filesystem::path path(wx_to_std(d->GetPath()));
493 if (path.extension() != ".pem") {
496 dcp::File f(path, "w");
498 throw OpenFileError(path, errno, OpenFileError::WRITE);
501 auto const s = _get()->chain();
502 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 = make_wx<MakeChainDialog>(this, _get());
549 if (d->ShowModal () == wxID_OK) {
551 update_certificate_list ();
552 update_private_key ();
557 CertificateChainEditor::update_sensitivity ()
559 /* We can only remove the leaf certificate */
560 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
561 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
565 CertificateChainEditor::update_private_key ()
567 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
572 CertificateChainEditor::import_private_key ()
574 auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
576 if (d->ShowModal() == wxID_OK) {
578 boost::filesystem::path p (wx_to_std (d->GetPath ()));
579 if (boost::filesystem::file_size (p) > 8192) {
582 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
587 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
588 chain->set_key (dcp::file_to_string (p));
590 update_private_key ();
591 } catch (std::exception& e) {
592 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
596 update_sensitivity ();
600 CertificateChainEditor::export_private_key ()
602 auto key = _get()->key();
607 auto d = make_wx<wxFileDialog>(
608 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
609 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
612 if (d->ShowModal () == wxID_OK) {
613 boost::filesystem::path path (wx_to_std(d->GetPath()));
614 if (path.extension() != ".pem") {
617 dcp::File f(path, "w");
619 throw OpenFileError (path, errno, OpenFileError::WRITE);
622 auto const s = _get()->key().get ();
623 f.checked_write(s.c_str(), s.length());
628 KeysPage::GetName () const
636 wxFont subheading_font (*wxNORMAL_FONT);
637 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
639 auto sizer = _panel->GetSizer();
642 auto m = new StaticText (_panel, _("Decrypting KDMs"));
643 m->SetFont (subheading_font);
644 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
647 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
649 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
650 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
651 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
652 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
653 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
654 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
655 auto decryption_advanced = new Button (_panel, _("Advanced..."));
656 kdm_buttons->Add (decryption_advanced, 0);
658 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
660 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
661 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
662 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
663 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
666 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
667 m->SetFont (subheading_font);
668 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
671 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
673 auto signing_advanced = new Button (_panel, _("Advanced..."));
674 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
675 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
676 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
678 sizer->Add (signing_buttons, 0, wxLEFT, _border);
680 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
681 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
686 KeysPage::remake_signing ()
688 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
690 if (d->ShowModal () == wxID_OK) {
691 Config::instance()->set_signer_chain(d->get());
697 KeysPage::decryption_advanced ()
699 auto c = new CertificateChainEditor (
700 _panel, _("Decrypting KDMs"), _border,
701 bind(&Config::set_decryption_chain, Config::instance(), _1),
702 bind(&Config::decryption_chain, Config::instance()),
703 bind(&KeysPage::nag_alter_decryption_chain, this)
710 KeysPage::signing_advanced ()
712 auto c = new CertificateChainEditor (
713 _panel, _("Signing DCPs and KDMs"), _border,
714 bind(&Config::set_signer_chain, Config::instance(), _1),
715 bind(&Config::signer_chain, Config::instance()),
723 KeysPage::export_decryption_chain_and_key ()
725 auto d = make_wx<wxFileDialog>(
726 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
727 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
730 if (d->ShowModal() != wxID_OK) {
734 boost::filesystem::path path(wx_to_std(d->GetPath()));
735 dcp::File f(path, "w");
737 throw OpenFileError(path, errno, OpenFileError::WRITE);
740 auto const chain = Config::instance()->decryption_chain()->chain();
741 f.checked_write(chain.c_str(), chain.length());
742 auto const key = Config::instance()->decryption_chain()->key();
743 DCPOMATIC_ASSERT(key);
744 f.checked_write(key->c_str(), key->length());
748 KeysPage::import_decryption_chain_and_key ()
750 if (NagDialog::maybe_nag (
752 Config::NAG_IMPORT_DECRYPTION_CHAIN,
753 _("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!"),
759 auto d = make_wx<wxFileDialog>(
760 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
763 if (d->ShowModal() != wxID_OK) {
767 auto new_chain = make_shared<dcp::CertificateChain>();
769 dcp::File f(wx_to_std(d->GetPath()), "r");
771 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
777 if (f.gets(buffer, 128) == 0) {
781 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
782 new_chain->add(dcp::Certificate(current));
784 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
785 new_chain->set_key(current);
790 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
791 Config::instance()->set_decryption_chain(new_chain);
793 error_dialog(_panel, _("Invalid DCP-o-matic export file"));
798 KeysPage::nag_alter_decryption_chain ()
800 return NagDialog::maybe_nag (
802 Config::NAG_ALTER_DECRYPTION_CHAIN,
803 _("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!"),
809 KeysPage::export_decryption_certificate ()
811 auto config = Config::instance();
812 wxString default_name = "dcpomatic";
813 if (!config->dcp_creator().empty()) {
814 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
816 if (!config->dcp_issuer().empty()) {
817 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
819 default_name += wxT("_kdm_decryption_cert.pem");
821 auto d = make_wx<wxFileDialog>(
822 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
823 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
826 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());
844 SoundPage::GetName () const
852 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
853 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
857 _sound = new CheckBox (_panel, _("Play sound via"));
858 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
859 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
860 _sound_output = new wxChoice (_panel, wxID_ANY);
861 s->Add (_sound_output, 0);
862 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
863 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
864 table->Add (s, wxGBPosition(r, 1));
867 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
868 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
869 _map->SetSize (-1, 400);
870 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
873 _reset_to_default = new Button (_panel, _("Reset to default"));
874 table->Add (_reset_to_default, wxGBPosition(r, 1));
877 wxFont font = _sound_output_details->GetFont();
878 font.SetStyle (wxFONTSTYLE_ITALIC);
879 font.SetPointSize (font.GetPointSize() - 1);
880 _sound_output_details->SetFont (font);
882 RtAudio audio (DCPOMATIC_RTAUDIO_API);
883 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
885 auto dev = audio.getDeviceInfo (i);
886 if (dev.probed && dev.outputChannels > 0) {
887 _sound_output->Append (std_to_wx (dev.name));
889 } catch (RtAudioError&) {
890 /* Something went wrong so let's just ignore that device */
894 _sound->bind(&SoundPage::sound_changed, this);
895 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
896 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
897 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
901 SoundPage::reset_to_default ()
903 Config::instance()->set_audio_mapping_to_default ();
907 SoundPage::map_changed (AudioMapping m)
909 Config::instance()->set_audio_mapping (m);
913 SoundPage::sound_changed ()
915 Config::instance()->set_sound (_sound->GetValue ());
919 SoundPage::sound_output_changed ()
921 RtAudio audio (DCPOMATIC_RTAUDIO_API);
922 auto const so = get_sound_output();
923 string default_device;
925 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
926 } catch (RtAudioError&) {
929 if (!so || *so == default_device) {
930 Config::instance()->unset_sound_output ();
932 Config::instance()->set_sound_output (*so);
937 SoundPage::config_changed ()
939 auto config = Config::instance ();
941 checked_set (_sound, config->sound ());
943 auto const current_so = get_sound_output ();
944 optional<string> configured_so;
946 if (config->sound_output()) {
947 configured_so = config->sound_output().get();
949 /* No configured output means we should use the default */
950 RtAudio audio (DCPOMATIC_RTAUDIO_API);
952 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
953 } catch (RtAudioError&) {
954 /* Probably no audio devices at all */
958 if (configured_so && current_so != configured_so) {
959 /* Update _sound_output with the configured value */
961 while (i < _sound_output->GetCount()) {
962 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
963 _sound_output->SetSelection (i);
970 RtAudio audio (DCPOMATIC_RTAUDIO_API);
972 map<int, wxString> apis;
973 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
974 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
975 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
976 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
977 apis[RtAudio::UNIX_JACK] = _("JACK");
978 apis[RtAudio::LINUX_ALSA] = _("ALSA");
979 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
980 apis[RtAudio::LINUX_OSS] = _("OSS");
981 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
985 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
987 auto info = audio.getDeviceInfo(i);
988 if (info.name == *configured_so && info.outputChannels > 0) {
989 channels = info.outputChannels;
991 } catch (RtAudioError&) {
997 _sound_output_details->SetLabel (
998 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1001 _map->set (Config::instance()->audio_mapping(channels));
1003 vector<NamedChannel> input;
1004 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1005 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1007 _map->set_input_channels (input);
1009 vector<NamedChannel> output;
1010 for (int i = 0; i < channels; ++i) {
1011 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1013 _map->set_output_channels (output);
1015 setup_sensitivity ();
1019 SoundPage::setup_sensitivity ()
1021 _sound_output->Enable (_sound->GetValue());
1024 /** @return Currently-selected preview sound output in the dialogue */
1026 SoundPage::get_sound_output ()
1028 int const sel = _sound_output->GetSelection ();
1029 if (sel == wxNOT_FOUND) {
1030 return optional<string> ();
1033 return wx_to_std (_sound_output->GetString (sel));
1037 LocationsPage::LocationsPage (wxSize panel_size, int border)
1038 : Page (panel_size, border)
1044 LocationsPage::GetName () const
1046 return _("Locations");
1049 #ifdef DCPOMATIC_OSX
1051 LocationsPage::GetLargeIcon () const
1053 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1058 LocationsPage::setup ()
1062 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1063 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1065 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1066 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1067 table->Add (_content_directory, wxGBPosition (r, 1));
1070 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1071 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1072 table->Add (_playlist_directory, wxGBPosition (r, 1));
1075 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1076 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1077 table->Add (_kdm_directory, wxGBPosition (r, 1));
1080 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1081 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1082 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1086 LocationsPage::config_changed ()
1088 auto config = Config::instance ();
1090 if (config->player_content_directory()) {
1091 checked_set (_content_directory, *config->player_content_directory());
1093 if (config->player_playlist_directory()) {
1094 checked_set (_playlist_directory, *config->player_playlist_directory());
1096 if (config->player_kdm_directory()) {
1097 checked_set (_kdm_directory, *config->player_kdm_directory());
1102 LocationsPage::content_directory_changed ()
1104 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1108 LocationsPage::playlist_directory_changed ()
1110 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1114 LocationsPage::kdm_directory_changed ()
1116 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));