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_api.h"
23 #include "audio_mapping_view.h"
24 #include "check_box.h"
25 #include "dcpomatic_choice.h"
26 #include "config_dialog.h"
27 #include "dcpomatic_button.h"
28 #include "film_viewer.h"
29 #include "nag_dialog.h"
30 #include "static_text.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("Français", "fr_FR"));
133 languages.push_back (make_pair("Italiano", "it_IT"));
134 languages.push_back (make_pair("Nederlands", "nl_NL"));
135 languages.push_back (make_pair("Русский", "ru_RU"));
136 languages.push_back (make_pair("Polski", "pl_PL"));
137 languages.push_back (make_pair("Português europeu", "pt_PT"));
138 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
139 languages.push_back (make_pair("Svenska", "sv_SE"));
140 languages.push_back (make_pair("Slovenščina", "sl_SI"));
141 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
142 // languages.push_back (make_pair("Türkçe", "tr_TR"));
143 languages.push_back (make_pair("українська мова", "uk_UA"));
144 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
145 checked_set (_language, languages);
146 table->Add (_language, wxGBPosition (r, 1));
149 auto restart = add_label_to_sizer (
150 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
152 wxFont font = restart->GetFont();
153 font.SetStyle (wxFONTSTYLE_ITALIC);
154 font.SetPointSize (font.GetPointSize() - 1);
155 restart->SetFont (font);
158 _set_language->bind(&GeneralPage::set_language_changed, this);
159 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
163 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
165 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
166 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
169 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
170 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
173 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
174 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
178 GeneralPage::config_changed ()
180 auto config = Config::instance ();
182 checked_set (_set_language, static_cast<bool>(config->language()));
184 /* Backwards compatibility of config file */
186 map<string, string> compat_map;
187 compat_map["fr"] = "fr_FR";
188 compat_map["it"] = "it_IT";
189 compat_map["es"] = "es_ES";
190 compat_map["sv"] = "sv_SE";
191 compat_map["de"] = "de_DE";
192 compat_map["nl"] = "nl_NL";
193 compat_map["ru"] = "ru_RU";
194 compat_map["pl"] = "pl_PL";
195 compat_map["da"] = "da_DK";
196 compat_map["pt"] = "pt_PT";
197 compat_map["sk"] = "sk_SK";
198 compat_map["cs"] = "cs_CZ";
199 compat_map["uk"] = "uk_UA";
201 auto lang = config->language().get_value_or("en_GB");
202 if (compat_map.find(lang) != compat_map.end ()) {
203 lang = compat_map[lang];
206 checked_set (_language, lang);
208 checked_set (_check_for_updates, config->check_for_updates ());
209 checked_set (_check_for_test_updates, config->check_for_test_updates ());
211 setup_sensitivity ();
215 GeneralPage::setup_sensitivity ()
217 _language->Enable (_set_language->GetValue ());
218 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
222 GeneralPage::set_language_changed ()
224 setup_sensitivity ();
225 if (_set_language->GetValue ()) {
228 Config::instance()->unset_language ();
233 GeneralPage::language_changed ()
235 int const sel = _language->GetSelection ();
237 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
239 Config::instance()->unset_language ();
244 GeneralPage::check_for_updates_changed ()
246 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
250 GeneralPage::check_for_test_updates_changed ()
252 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
255 CertificateChainEditor::CertificateChainEditor (
259 function<void (shared_ptr<dcp::CertificateChain>)> set,
260 function<shared_ptr<const dcp::CertificateChain> (void)> get,
261 function<bool (void)> nag_alter
263 : wxDialog (parent, wxID_ANY, title)
266 , _nag_alter (nag_alter)
268 _sizer = new wxBoxSizer (wxVERTICAL);
270 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
271 _sizer->Add (certificates_sizer, 0, wxALL, border);
273 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
278 ip.SetText (_("Type"));
280 _certificates->InsertColumn (0, ip);
286 ip.SetText (_("Thumbprint"));
289 wxFont font = ip.GetFont ();
290 font.SetFamily (wxFONTFAMILY_TELETYPE);
293 _certificates->InsertColumn (1, ip);
296 certificates_sizer->Add (_certificates, 1, wxEXPAND);
299 auto s = new wxBoxSizer (wxVERTICAL);
300 _add_certificate = new Button (this, _("Add..."));
301 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
302 _remove_certificate = new Button (this, _("Remove"));
303 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304 _export_certificate = new Button (this, _("Export certificate..."));
305 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
306 _export_chain = new Button (this, _("Export chain..."));
307 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
308 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
311 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
312 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
315 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
316 _private_key = new StaticText (this, wxT(""));
317 wxFont font = _private_key->GetFont ();
318 font.SetFamily (wxFONTFAMILY_TELETYPE);
319 _private_key->SetFont (font);
320 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
321 _import_private_key = new Button (this, _("Import..."));
322 table->Add (_import_private_key, wxGBPosition (r, 2));
323 _export_private_key = new Button (this, _("Export..."));
324 table->Add (_export_private_key, wxGBPosition (r, 3));
327 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
328 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
329 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
330 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
333 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
334 font = *wxSMALL_FONT;
335 font.SetWeight (wxFONTWEIGHT_BOLD);
336 _private_key_bad->SetFont (font);
337 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
340 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
341 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
342 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
343 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
344 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
345 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
346 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
347 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
348 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
350 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
352 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
355 SetSizerAndFit (_sizer);
357 update_certificate_list ();
358 update_private_key ();
359 update_sensitivity ();
363 CertificateChainEditor::add_button (wxWindow* button)
365 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
370 CertificateChainEditor::add_certificate ()
372 auto d = new wxFileDialog (this, _("Select Certificate File"));
374 if (d->ShowModal() == wxID_OK) {
379 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
380 } catch (boost::filesystem::filesystem_error& e) {
381 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
386 if (!extra.empty ()) {
389 _("This file contains other certificates (or other data) after its first certificate. "
390 "Only the first certificate will be used.")
393 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
395 if (!chain->chain_valid ()) {
398 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
399 "Add certificates in order from root to intermediate to leaf.")
404 update_certificate_list ();
406 } catch (dcp::MiscError& e) {
407 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
413 update_sensitivity ();
417 CertificateChainEditor::remove_certificate ()
420 /* Cancel was clicked */
424 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
429 _certificates->DeleteItem (i);
430 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
434 update_sensitivity ();
435 update_certificate_list ();
439 CertificateChainEditor::export_certificate ()
441 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
446 auto all = _get()->root_to_leaf();
448 wxString default_name;
450 default_name = "root.pem";
451 } else if (i == static_cast<int>(all.size() - 1)) {
452 default_name = "leaf.pem";
454 default_name = "intermediate.pem";
457 auto d = new wxFileDialog(
458 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
459 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
462 auto j = all.begin ();
463 for (int k = 0; k < i; ++k) {
467 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());
484 CertificateChainEditor::export_chain ()
486 auto d = new 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) {
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());
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 = new MakeChainDialog (this, _get());
552 if (d->ShowModal () == wxID_OK) {
554 update_certificate_list ();
555 update_private_key ();
562 CertificateChainEditor::update_sensitivity ()
564 /* We can only remove the leaf certificate */
565 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
566 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
570 CertificateChainEditor::update_private_key ()
572 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
577 CertificateChainEditor::import_private_key ()
579 auto d = new wxFileDialog (this, _("Select Key File"));
581 if (d->ShowModal() == wxID_OK) {
583 boost::filesystem::path p (wx_to_std (d->GetPath ()));
584 if (boost::filesystem::file_size (p) > 8192) {
587 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
592 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
593 chain->set_key (dcp::file_to_string (p));
595 update_private_key ();
596 } catch (std::exception& e) {
597 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
603 update_sensitivity ();
607 CertificateChainEditor::export_private_key ()
609 auto key = _get()->key();
614 auto d = new wxFileDialog (
615 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
616 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
619 if (d->ShowModal () == wxID_OK) {
620 boost::filesystem::path path (wx_to_std(d->GetPath()));
621 if (path.extension() != ".pem") {
624 dcp::File f(path, "w");
626 throw OpenFileError (path, errno, OpenFileError::WRITE);
629 auto const s = _get()->key().get ();
630 f.checked_write(s.c_str(), s.length());
636 KeysPage::GetName () const
644 wxFont subheading_font (*wxNORMAL_FONT);
645 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
647 auto sizer = _panel->GetSizer();
650 auto m = new StaticText (_panel, _("Decrypting KDMs"));
651 m->SetFont (subheading_font);
652 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
655 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
657 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
658 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
659 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
660 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
661 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
662 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
663 auto decryption_advanced = new Button (_panel, _("Advanced..."));
664 kdm_buttons->Add (decryption_advanced, 0);
666 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
668 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
669 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
670 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
671 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
674 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
675 m->SetFont (subheading_font);
676 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
679 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
681 auto signing_advanced = new Button (_panel, _("Advanced..."));
682 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
683 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
684 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
686 sizer->Add (signing_buttons, 0, wxLEFT, _border);
688 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
689 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
694 KeysPage::remake_signing ()
696 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
698 if (d->ShowModal () == wxID_OK) {
699 Config::instance()->set_signer_chain(d->get());
705 KeysPage::decryption_advanced ()
707 auto c = new CertificateChainEditor (
708 _panel, _("Decrypting KDMs"), _border,
709 bind(&Config::set_decryption_chain, Config::instance(), _1),
710 bind(&Config::decryption_chain, Config::instance()),
711 bind(&KeysPage::nag_alter_decryption_chain, this)
718 KeysPage::signing_advanced ()
720 auto c = new CertificateChainEditor (
721 _panel, _("Signing DCPs and KDMs"), _border,
722 bind(&Config::set_signer_chain, Config::instance(), _1),
723 bind(&Config::signer_chain, Config::instance()),
731 KeysPage::export_decryption_chain_and_key ()
733 auto d = new wxFileDialog (
734 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
735 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
738 if (d->ShowModal () == wxID_OK) {
739 boost::filesystem::path path (wx_to_std(d->GetPath()));
740 dcp::File f(path, "w");
742 throw OpenFileError (path, errno, OpenFileError::WRITE);
745 auto const chain = Config::instance()->decryption_chain()->chain();
746 f.checked_write (chain.c_str(), chain.length());
747 auto const key = Config::instance()->decryption_chain()->key();
748 DCPOMATIC_ASSERT (key);
749 f.checked_write(key->c_str(), key->length());
756 KeysPage::import_decryption_chain_and_key ()
758 if (NagDialog::maybe_nag (
760 Config::NAG_IMPORT_DECRYPTION_CHAIN,
761 _("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!"),
767 auto d = new wxFileDialog (
768 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
771 if (d->ShowModal () == wxID_OK) {
772 auto new_chain = make_shared<dcp::CertificateChain>();
774 dcp::File f(wx_to_std(d->GetPath()), "r");
776 throw OpenFileError (f.path(), errno, OpenFileError::WRITE);
782 if (f.gets(buffer, 128) == 0) {
786 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
787 new_chain->add (dcp::Certificate (current));
789 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
790 new_chain->set_key (current);
795 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
796 Config::instance()->set_decryption_chain (new_chain);
798 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
833 if (d->ShowModal () == wxID_OK) {
834 boost::filesystem::path path (wx_to_std(d->GetPath()));
835 if (path.extension() != ".pem") {
838 dcp::File f(path, "w");
840 throw OpenFileError (path, errno, OpenFileError::WRITE);
843 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
844 f.checked_write(s.c_str(), s.length());
851 SoundPage::GetName () const
859 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
860 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
864 _sound = new CheckBox (_panel, _("Play sound via"));
865 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
866 auto s = new wxBoxSizer (wxHORIZONTAL);
867 _sound_api = new Choice(_panel);
869 _sound_output = new wxChoice (_panel, wxID_ANY);
870 s->Add(_sound_output, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
871 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
872 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
873 table->Add (s, wxGBPosition(r, 1));
876 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
877 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
878 _map->SetSize (-1, 400);
879 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
882 _reset_to_default = new Button (_panel, _("Reset to default"));
883 table->Add (_reset_to_default, wxGBPosition(r, 1));
886 wxFont font = _sound_output_details->GetFont();
887 font.SetStyle (wxFONTSTYLE_ITALIC);
888 font.SetPointSize (font.GetPointSize() - 1);
889 _sound_output_details->SetFont (font);
891 for (auto const& api: audio_apis()) {
892 _sound_api->add(api.name(), new wxStringClientData(api.id()));
895 auto& audio = _viewer->audio_backend();
896 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
898 auto dev = audio.getDeviceInfo (i);
899 if (dev.probed && dev.outputChannels > 0) {
900 _sound_output->Append (std_to_wx (dev.name));
902 } catch (RtAudioError&) {
903 /* Something went wrong so let's just ignore that device */
907 _sound->bind(&SoundPage::sound_changed, this);
908 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
909 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
910 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
914 SoundPage::reset_to_default ()
916 Config::instance()->set_audio_mapping_to_default ();
920 SoundPage::map_changed (AudioMapping m)
922 Config::instance()->set_audio_mapping (m);
926 SoundPage::sound_changed ()
928 Config::instance()->set_sound (_sound->GetValue ());
932 SoundPage::sound_output_changed ()
934 auto& audio = _viewer->audio_backend();
935 auto const so = get_sound_output();
936 string default_device;
938 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
939 } catch (RtAudioError&) {
942 if (!so || *so == default_device) {
943 Config::instance()->unset_sound_output ();
945 Config::instance()->set_sound_output (*so);
950 SoundPage::config_changed ()
952 auto config = Config::instance ();
954 checked_set (_sound, config->sound ());
956 auto const current_api = get_sound_api();
957 auto const configured_api = id_to_audio_api(config->sound_api()).id();
959 if (current_api != configured_api) {
960 /* Update _sound_api with the configured value */
961 unsigned int index = 0;
962 while (index < _sound_api->GetCount()) {
963 if (string_client_data(_sound_api->GetClientObject(index)) == configured_api) {
964 _sound_api->SetSelection(index);
970 auto const current_so = get_sound_output ();
971 optional<string> configured_so;
973 auto& audio = _viewer->audio_backend();
975 if (config->sound_output()) {
976 configured_so = config->sound_output().get();
978 /* No configured output means we should use the default */
980 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
981 } catch (RtAudioError&) {
982 /* Probably no audio devices at all */
986 if (configured_so && current_so != configured_so) {
987 /* Update _sound_output with the configured value */
989 while (i < _sound_output->GetCount()) {
990 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
991 _sound_output->SetSelection (i);
998 map<int, wxString> apis;
999 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
1000 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
1001 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
1002 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
1003 apis[RtAudio::UNIX_JACK] = _("JACK");
1004 apis[RtAudio::LINUX_ALSA] = _("ALSA");
1005 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1006 apis[RtAudio::LINUX_OSS] = _("OSS");
1007 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1010 if (configured_so) {
1011 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1013 auto info = audio.getDeviceInfo(i);
1014 if (info.name == *configured_so && info.outputChannels > 0) {
1015 channels = info.outputChannels;
1017 } catch (RtAudioError&) {
1023 _sound_output_details->SetLabel (
1024 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1027 _map->set (Config::instance()->audio_mapping(channels));
1029 vector<NamedChannel> input;
1030 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1031 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1033 _map->set_input_channels (input);
1035 vector<NamedChannel> output;
1036 for (int i = 0; i < channels; ++i) {
1037 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1039 _map->set_output_channels (output);
1041 setup_sensitivity ();
1045 SoundPage::setup_sensitivity ()
1047 _sound_output->Enable (_sound->GetValue());
1052 SoundPage::get_sound_api() const
1054 auto const sel = _sound_api->get();
1059 return index_to_audio_api(*sel).id();
1063 /** @return Currently-selected preview sound output in the dialogue */
1065 SoundPage::get_sound_output() const
1067 int const sel = _sound_output->GetSelection ();
1068 if (sel == wxNOT_FOUND) {
1072 return wx_to_std (_sound_output->GetString (sel));
1076 LocationsPage::LocationsPage (wxSize panel_size, int border)
1077 : Page (panel_size, border)
1083 LocationsPage::GetName () const
1085 return _("Locations");
1088 #ifdef DCPOMATIC_OSX
1090 LocationsPage::GetLargeIcon () const
1092 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1097 LocationsPage::setup ()
1101 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1102 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1104 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1105 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1106 table->Add (_content_directory, wxGBPosition (r, 1));
1109 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1110 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1111 table->Add (_playlist_directory, wxGBPosition (r, 1));
1114 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1115 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1116 table->Add (_kdm_directory, wxGBPosition (r, 1));
1119 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1120 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1121 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1125 LocationsPage::config_changed ()
1127 auto config = Config::instance ();
1129 if (config->player_content_directory()) {
1130 checked_set (_content_directory, *config->player_content_directory());
1132 if (config->player_playlist_directory()) {
1133 checked_set (_playlist_directory, *config->player_playlist_directory());
1135 if (config->player_kdm_directory()) {
1136 checked_set (_kdm_directory, *config->player_kdm_directory());
1141 LocationsPage::content_directory_changed ()
1143 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1147 LocationsPage::playlist_directory_changed ()
1149 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1153 LocationsPage::kdm_directory_changed ()
1155 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));