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/filesystem.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("ქართული", "ka_KA"));
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 = make_wx<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());
385 if (!extra.empty ()) {
388 _("This file contains other certificates (or other data) after its first certificate. "
389 "Only the first certificate will be used.")
392 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
394 if (!chain->chain_valid ()) {
397 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
398 "Add certificates in order from root to intermediate to leaf.")
403 update_certificate_list ();
405 } catch (dcp::MiscError& e) {
406 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 = make_wx<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) {
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 = make_wx<wxFileDialog>(
485 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
486 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
489 if (d->ShowModal() != wxID_OK) {
493 boost::filesystem::path path(wx_to_std(d->GetPath()));
494 if (path.extension() != ".pem") {
497 dcp::File f(path, "w");
499 throw OpenFileError(path, errno, OpenFileError::WRITE);
502 auto const s = _get()->chain();
503 f.checked_write(s.c_str(), s.length());
507 CertificateChainEditor::update_certificate_list ()
509 _certificates->DeleteAllItems ();
511 auto certs = _get()->root_to_leaf();
512 for (auto const& i: certs) {
515 _certificates->InsertItem (item);
516 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
519 _certificates->SetItem (n, 0, _("Root"));
520 } else if (n == (certs.size() - 1)) {
521 _certificates->SetItem (n, 0, _("Leaf"));
523 _certificates->SetItem (n, 0, _("Intermediate"));
529 static wxColour normal = _private_key_bad->GetForegroundColour ();
531 if (_get()->private_key_valid()) {
532 _private_key_bad->Hide ();
533 _private_key_bad->SetForegroundColour (normal);
535 _private_key_bad->Show ();
536 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
541 CertificateChainEditor::remake_certificates ()
544 /* Cancel was clicked */
548 auto d = make_wx<MakeChainDialog>(this, _get());
550 if (d->ShowModal () == wxID_OK) {
552 update_certificate_list ();
553 update_private_key ();
558 CertificateChainEditor::update_sensitivity ()
560 /* We can only remove the leaf certificate */
561 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
562 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
566 CertificateChainEditor::update_private_key ()
568 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
573 CertificateChainEditor::import_private_key ()
575 auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
577 if (d->ShowModal() == wxID_OK) {
579 boost::filesystem::path p (wx_to_std (d->GetPath ()));
580 if (dcp::filesystem::file_size(p) > 8192) {
583 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
588 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
589 chain->set_key (dcp::file_to_string (p));
591 update_private_key ();
592 } catch (std::exception& e) {
593 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
597 update_sensitivity ();
601 CertificateChainEditor::export_private_key ()
603 auto key = _get()->key();
608 auto d = make_wx<wxFileDialog>(
609 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
610 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
613 if (d->ShowModal () == wxID_OK) {
614 boost::filesystem::path path (wx_to_std(d->GetPath()));
615 if (path.extension() != ".pem") {
618 dcp::File f(path, "w");
620 throw OpenFileError (path, errno, OpenFileError::WRITE);
623 auto const s = _get()->key().get ();
624 f.checked_write(s.c_str(), s.length());
629 KeysPage::GetName () const
637 wxFont subheading_font (*wxNORMAL_FONT);
638 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
640 auto sizer = _panel->GetSizer();
643 auto m = new StaticText (_panel, _("Decrypting KDMs"));
644 m->SetFont (subheading_font);
645 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
648 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
650 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
651 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
652 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
653 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
655 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656 auto decryption_advanced = new Button (_panel, _("Advanced..."));
657 kdm_buttons->Add (decryption_advanced, 0);
659 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
661 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
662 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
663 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
664 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
667 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
668 m->SetFont (subheading_font);
669 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
672 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
674 auto signing_advanced = new Button (_panel, _("Advanced..."));
675 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
676 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
677 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
679 sizer->Add (signing_buttons, 0, wxLEFT, _border);
681 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
682 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
687 KeysPage::remake_signing ()
689 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
691 if (d->ShowModal () == wxID_OK) {
692 Config::instance()->set_signer_chain(d->get());
698 KeysPage::decryption_advanced ()
700 auto c = new CertificateChainEditor (
701 _panel, _("Decrypting KDMs"), _border,
702 bind(&Config::set_decryption_chain, Config::instance(), _1),
703 bind(&Config::decryption_chain, Config::instance()),
704 bind(&KeysPage::nag_alter_decryption_chain, this)
711 KeysPage::signing_advanced ()
713 auto c = new CertificateChainEditor (
714 _panel, _("Signing DCPs and KDMs"), _border,
715 bind(&Config::set_signer_chain, Config::instance(), _1),
716 bind(&Config::signer_chain, Config::instance()),
724 KeysPage::export_decryption_chain_and_key ()
726 auto d = make_wx<wxFileDialog>(
727 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
728 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
731 if (d->ShowModal() != wxID_OK) {
735 boost::filesystem::path path(wx_to_std(d->GetPath()));
736 dcp::File f(path, "w");
738 throw OpenFileError(path, errno, OpenFileError::WRITE);
741 auto const chain = Config::instance()->decryption_chain()->chain();
742 f.checked_write(chain.c_str(), chain.length());
743 auto const key = Config::instance()->decryption_chain()->key();
744 DCPOMATIC_ASSERT(key);
745 f.checked_write(key->c_str(), key->length());
749 KeysPage::import_decryption_chain_and_key ()
751 if (NagDialog::maybe_nag (
753 Config::NAG_IMPORT_DECRYPTION_CHAIN,
754 _("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!"),
760 auto d = make_wx<wxFileDialog>(
761 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
764 if (d->ShowModal() != wxID_OK) {
768 auto new_chain = make_shared<dcp::CertificateChain>();
770 dcp::File f(wx_to_std(d->GetPath()), "r");
772 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
778 if (f.gets(buffer, 128) == 0) {
782 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
783 new_chain->add(dcp::Certificate(current));
785 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
786 new_chain->set_key(current);
791 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
792 Config::instance()->set_decryption_chain(new_chain);
794 error_dialog(_panel, _("Invalid DCP-o-matic export file"));
799 KeysPage::nag_alter_decryption_chain ()
801 return NagDialog::maybe_nag (
803 Config::NAG_ALTER_DECRYPTION_CHAIN,
804 _("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!"),
810 KeysPage::export_decryption_certificate ()
812 auto config = Config::instance();
813 wxString default_name = "dcpomatic";
814 if (!config->dcp_creator().empty()) {
815 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
817 if (!config->dcp_issuer().empty()) {
818 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
820 default_name += wxT("_kdm_decryption_cert.pem");
822 auto d = make_wx<wxFileDialog>(
823 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
824 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
827 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());
845 SoundPage::GetName () const
853 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
854 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
858 _sound = new CheckBox (_panel, _("Play sound via"));
859 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
860 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
861 _sound_output = new wxChoice (_panel, wxID_ANY);
862 s->Add (_sound_output, 0);
863 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
864 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
865 table->Add (s, wxGBPosition(r, 1));
868 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
869 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
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()));