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 #if (RTAUDIO_VERSION_MAJOR >= 6)
884 for (auto device_id: audio.getDeviceIds()) {
885 auto dev = audio.getDeviceInfo(device_id);
886 if (dev.outputChannels > 0) {
887 _sound_output->Append(std_to_wx(dev.name));
891 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
893 auto dev = audio.getDeviceInfo (i);
894 if (dev.probed && dev.outputChannels > 0) {
895 _sound_output->Append (std_to_wx (dev.name));
897 } catch (RtAudioError&) {
898 /* Something went wrong so let's just ignore that device */
903 _sound->bind(&SoundPage::sound_changed, this);
904 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
905 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
906 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
910 SoundPage::reset_to_default ()
912 Config::instance()->set_audio_mapping_to_default ();
916 SoundPage::map_changed (AudioMapping m)
918 Config::instance()->set_audio_mapping (m);
922 SoundPage::sound_changed ()
924 Config::instance()->set_sound (_sound->GetValue ());
928 SoundPage::sound_output_changed ()
930 RtAudio audio (DCPOMATIC_RTAUDIO_API);
931 auto const so = get_sound_output();
932 string default_device;
933 #if (RTAUDIO_VERSION_MAJOR >= 6)
934 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
937 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
938 } 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_so = get_sound_output ();
957 optional<string> configured_so;
959 if (config->sound_output()) {
960 configured_so = config->sound_output().get();
962 /* No configured output means we should use the default */
963 RtAudio audio (DCPOMATIC_RTAUDIO_API);
964 #if (RTAUDIO_VERSION_MAJOR >= 6)
965 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
968 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
969 } catch (RtAudioError&) {
970 /* Probably no audio devices at all */
975 if (configured_so && current_so != configured_so) {
976 /* Update _sound_output with the configured value */
978 while (i < _sound_output->GetCount()) {
979 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
980 _sound_output->SetSelection (i);
987 RtAudio audio (DCPOMATIC_RTAUDIO_API);
989 map<int, wxString> apis;
990 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
991 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
992 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
993 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
994 apis[RtAudio::UNIX_JACK] = _("JACK");
995 apis[RtAudio::LINUX_ALSA] = _("ALSA");
996 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
997 apis[RtAudio::LINUX_OSS] = _("OSS");
998 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1001 if (configured_so) {
1002 #if (RTAUDIO_VERSION_MAJOR >= 6)
1003 for (auto device_id: audio.getDeviceIds()) {
1004 auto info = audio.getDeviceInfo(device_id);
1005 if (info.name == *configured_so && info.outputChannels > 0) {
1006 channels = info.outputChannels;
1010 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1012 auto info = audio.getDeviceInfo(i);
1013 if (info.name == *configured_so && info.outputChannels > 0) {
1014 channels = info.outputChannels;
1016 } 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());
1050 /** @return Currently-selected preview sound output in the dialogue */
1052 SoundPage::get_sound_output ()
1054 int const sel = _sound_output->GetSelection ();
1055 if (sel == wxNOT_FOUND) {
1056 return optional<string> ();
1059 return wx_to_std (_sound_output->GetString (sel));
1063 LocationsPage::LocationsPage (wxSize panel_size, int border)
1064 : Page (panel_size, border)
1070 LocationsPage::GetName () const
1072 return _("Locations");
1075 #ifdef DCPOMATIC_OSX
1077 LocationsPage::GetLargeIcon () const
1079 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1084 LocationsPage::setup ()
1088 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1089 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1091 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1092 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1093 table->Add (_content_directory, wxGBPosition (r, 1));
1096 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1097 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1098 table->Add (_playlist_directory, wxGBPosition (r, 1));
1101 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1102 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1103 table->Add (_kdm_directory, wxGBPosition (r, 1));
1106 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1107 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1108 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1112 LocationsPage::config_changed ()
1114 auto config = Config::instance ();
1116 if (config->player_content_directory()) {
1117 checked_set (_content_directory, *config->player_content_directory());
1119 if (config->player_playlist_directory()) {
1120 checked_set (_playlist_directory, *config->player_playlist_directory());
1122 if (config->player_kdm_directory()) {
1123 checked_set (_kdm_directory, *config->player_kdm_directory());
1128 LocationsPage::content_directory_changed ()
1130 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1134 LocationsPage::playlist_directory_changed ()
1136 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1140 LocationsPage::kdm_directory_changed ()
1142 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));