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 "wx_variant.h"
29 #include "lib/constants.h"
32 #include <dcp/filesystem.h>
33 #include <dcp/raw_convert.h>
38 using std::make_shared;
41 using std::shared_ptr;
45 using boost::optional;
46 #if BOOST_VERSION >= 106100
47 using namespace boost::placeholders;
58 Page::Page (wxSize panel_size, int border)
61 , _panel_size (panel_size)
62 , _window_exists (false)
64 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
69 Page::CreateWindow (wxWindow* parent)
71 return create_window (parent);
76 Page::create_window (wxWindow* parent)
78 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
79 auto s = new wxBoxSizer (wxVERTICAL);
83 _window_exists = true;
86 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
92 Page::config_changed_wrapper ()
100 Page::window_destroyed ()
102 _window_exists = false;
106 GeneralPage::GeneralPage (wxSize panel_size, int border)
107 : Page (panel_size, border)
114 GeneralPage::GetName () const
121 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
123 _set_language = new CheckBox (_panel, _("Set language"));
124 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
125 _language = new wxChoice (_panel, wxID_ANY);
126 vector<pair<string, string>> languages;
127 languages.push_back (make_pair("Čeština", "cs_CZ"));
128 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
129 languages.push_back (make_pair("Dansk", "da_DK"));
130 languages.push_back (make_pair("Deutsch", "de_DE"));
131 languages.push_back (make_pair("English", "en_GB"));
132 languages.push_back (make_pair("Español", "es_ES"));
133 languages.push_back (make_pair("فارسی", "fa_IR"));
134 languages.push_back (make_pair("Français", "fr_FR"));
135 languages.push_back (make_pair("Italiano", "it_IT"));
136 languages.push_back (make_pair("ქართული", "ka_KA"));
137 languages.push_back (make_pair("Nederlands", "nl_NL"));
138 languages.push_back (make_pair("Русский", "ru_RU"));
139 languages.push_back (make_pair("Polski", "pl_PL"));
140 languages.push_back (make_pair("Português europeu", "pt_PT"));
141 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
142 languages.push_back (make_pair("Svenska", "sv_SE"));
143 languages.push_back (make_pair("Slovenščina", "sl_SI"));
144 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
145 // languages.push_back (make_pair("Türkçe", "tr_TR"));
146 languages.push_back (make_pair("українська мова", "uk_UA"));
147 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
148 checked_set (_language, languages);
149 table->Add (_language, wxGBPosition (r, 1));
152 auto restart = add_label_to_sizer (
153 table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to see language changes)")), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
155 wxFont font = restart->GetFont();
156 font.SetStyle (wxFONTSTYLE_ITALIC);
157 font.SetPointSize (font.GetPointSize() - 1);
158 restart->SetFont (font);
161 _set_language->bind(&GeneralPage::set_language_changed, this);
162 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
166 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
168 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
169 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
172 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
173 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
176 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
177 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
181 GeneralPage::config_changed ()
183 auto config = Config::instance ();
185 checked_set (_set_language, static_cast<bool>(config->language()));
187 /* Backwards compatibility of config file */
189 map<string, string> compat_map;
190 compat_map["fr"] = "fr_FR";
191 compat_map["it"] = "it_IT";
192 compat_map["es"] = "es_ES";
193 compat_map["sv"] = "sv_SE";
194 compat_map["de"] = "de_DE";
195 compat_map["nl"] = "nl_NL";
196 compat_map["ru"] = "ru_RU";
197 compat_map["pl"] = "pl_PL";
198 compat_map["da"] = "da_DK";
199 compat_map["pt"] = "pt_PT";
200 compat_map["sk"] = "sk_SK";
201 compat_map["cs"] = "cs_CZ";
202 compat_map["uk"] = "uk_UA";
204 auto lang = config->language().get_value_or("en_GB");
205 if (compat_map.find(lang) != compat_map.end ()) {
206 lang = compat_map[lang];
209 checked_set (_language, lang);
211 checked_set (_check_for_updates, config->check_for_updates ());
212 checked_set (_check_for_test_updates, config->check_for_test_updates ());
214 setup_sensitivity ();
218 GeneralPage::setup_sensitivity ()
220 _language->Enable (_set_language->GetValue ());
221 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
225 GeneralPage::set_language_changed ()
227 setup_sensitivity ();
228 if (_set_language->GetValue ()) {
231 Config::instance()->unset_language ();
236 GeneralPage::language_changed ()
238 int const sel = _language->GetSelection ();
240 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
242 Config::instance()->unset_language ();
247 GeneralPage::check_for_updates_changed ()
249 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
253 GeneralPage::check_for_test_updates_changed ()
255 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
258 CertificateChainEditor::CertificateChainEditor (
262 function<void (shared_ptr<dcp::CertificateChain>)> set,
263 function<shared_ptr<const dcp::CertificateChain> (void)> get,
264 function<bool (void)> nag_alter
266 : wxDialog (parent, wxID_ANY, title)
269 , _nag_alter (nag_alter)
271 _sizer = new wxBoxSizer (wxVERTICAL);
273 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
274 _sizer->Add (certificates_sizer, 0, wxALL, border);
276 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
281 ip.SetText (_("Type"));
283 _certificates->InsertColumn (0, ip);
289 ip.SetText (_("Thumbprint"));
292 wxFont font = ip.GetFont ();
293 font.SetFamily (wxFONTFAMILY_TELETYPE);
296 _certificates->InsertColumn (1, ip);
299 certificates_sizer->Add (_certificates, 1, wxEXPAND);
302 auto s = new wxBoxSizer (wxVERTICAL);
303 _add_certificate = new Button (this, _("Add..."));
304 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
305 _remove_certificate = new Button (this, _("Remove"));
306 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
307 _export_certificate = new Button (this, _("Export certificate..."));
308 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
309 _export_chain = new Button (this, _("Export chain..."));
310 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
311 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
314 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
315 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
318 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
319 _private_key = new StaticText (this, wxT(""));
320 wxFont font = _private_key->GetFont ();
321 font.SetFamily (wxFONTFAMILY_TELETYPE);
322 _private_key->SetFont (font);
323 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
324 _import_private_key = new Button (this, _("Import..."));
325 table->Add (_import_private_key, wxGBPosition (r, 2));
326 _export_private_key = new Button (this, _("Export..."));
327 table->Add (_export_private_key, wxGBPosition (r, 3));
330 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
331 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
332 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
333 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
336 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
337 font = *wxSMALL_FONT;
338 font.SetWeight (wxFONTWEIGHT_BOLD);
339 _private_key_bad->SetFont (font);
340 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
343 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
344 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
345 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
346 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
347 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
348 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
349 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
350 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
351 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
353 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
355 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
358 SetSizerAndFit (_sizer);
360 update_certificate_list ();
361 update_private_key ();
362 update_sensitivity ();
366 CertificateChainEditor::add_button (wxWindow* button)
368 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
373 CertificateChainEditor::add_certificate ()
375 auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
377 if (d->ShowModal() == wxID_OK) {
382 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
383 } catch (boost::filesystem::filesystem_error& e) {
384 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
388 if (!extra.empty ()) {
391 _("This file contains other certificates (or other data) after its first certificate. "
392 "Only the first certificate will be used.")
395 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
397 if (!chain->chain_valid ()) {
400 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
401 "Add certificates in order from root to intermediate to leaf.")
406 update_certificate_list ();
408 } catch (dcp::MiscError& e) {
409 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 = make_wx<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) {
471 boost::filesystem::path path(wx_to_std(d->GetPath()));
472 if (path.extension() != ".pem") {
475 dcp::File f(path, "w");
477 throw OpenFileError(path, errno, OpenFileError::WRITE);
480 string const s = j->certificate(true);
481 f.checked_write(s.c_str(), s.length());
485 CertificateChainEditor::export_chain ()
487 auto d = make_wx<wxFileDialog>(
488 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
489 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
492 if (d->ShowModal() != wxID_OK) {
496 boost::filesystem::path path(wx_to_std(d->GetPath()));
497 if (path.extension() != ".pem") {
500 dcp::File f(path, "w");
502 throw OpenFileError(path, errno, OpenFileError::WRITE);
505 auto const s = _get()->chain();
506 f.checked_write(s.c_str(), s.length());
510 CertificateChainEditor::update_certificate_list ()
512 _certificates->DeleteAllItems ();
514 auto certs = _get()->root_to_leaf();
515 for (auto const& i: certs) {
518 _certificates->InsertItem (item);
519 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
522 _certificates->SetItem (n, 0, _("Root"));
523 } else if (n == (certs.size() - 1)) {
524 _certificates->SetItem (n, 0, _("Leaf"));
526 _certificates->SetItem (n, 0, _("Intermediate"));
532 static wxColour normal = _private_key_bad->GetForegroundColour ();
534 if (_get()->private_key_valid()) {
535 _private_key_bad->Hide ();
536 _private_key_bad->SetForegroundColour (normal);
538 _private_key_bad->Show ();
539 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
544 CertificateChainEditor::remake_certificates ()
547 /* Cancel was clicked */
551 auto d = make_wx<MakeChainDialog>(this, _get());
553 if (d->ShowModal () == wxID_OK) {
555 update_certificate_list ();
556 update_private_key ();
561 CertificateChainEditor::update_sensitivity ()
563 /* We can only remove the leaf certificate */
564 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
565 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
569 CertificateChainEditor::update_private_key ()
571 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
576 CertificateChainEditor::import_private_key ()
578 auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
580 if (d->ShowModal() == wxID_OK) {
582 boost::filesystem::path p (wx_to_std (d->GetPath ()));
583 if (dcp::filesystem::file_size(p) > 8192) {
586 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
591 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
592 chain->set_key (dcp::file_to_string (p));
594 update_private_key ();
595 } catch (std::exception& e) {
596 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
600 update_sensitivity ();
604 CertificateChainEditor::export_private_key ()
606 auto key = _get()->key();
611 auto d = make_wx<wxFileDialog>(
612 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
613 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
616 if (d->ShowModal () == wxID_OK) {
617 boost::filesystem::path path (wx_to_std(d->GetPath()));
618 if (path.extension() != ".pem") {
621 dcp::File f(path, "w");
623 throw OpenFileError (path, errno, OpenFileError::WRITE);
626 auto const s = _get()->key().get ();
627 f.checked_write(s.c_str(), s.length());
632 KeysPage::GetName () const
640 wxFont subheading_font (*wxNORMAL_FONT);
641 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
643 auto sizer = _panel->GetSizer();
646 auto m = new StaticText (_panel, _("Decrypting KDMs"));
647 m->SetFont (subheading_font);
648 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
651 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
653 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
654 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
655 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
656 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
657 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
658 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
659 auto decryption_advanced = new Button (_panel, _("Advanced..."));
660 kdm_buttons->Add (decryption_advanced, 0);
662 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
664 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
665 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
666 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
667 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
670 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
671 m->SetFont (subheading_font);
672 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
675 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
677 auto signing_advanced = new Button (_panel, _("Advanced..."));
678 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
679 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
680 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
682 sizer->Add (signing_buttons, 0, wxLEFT, _border);
684 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
685 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
690 KeysPage::remake_signing ()
692 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
694 if (d->ShowModal () == wxID_OK) {
695 Config::instance()->set_signer_chain(d->get());
701 KeysPage::decryption_advanced ()
703 auto c = new CertificateChainEditor (
704 _panel, _("Decrypting KDMs"), _border,
705 bind(&Config::set_decryption_chain, Config::instance(), _1),
706 bind(&Config::decryption_chain, Config::instance()),
707 bind(&KeysPage::nag_alter_decryption_chain, this)
714 KeysPage::signing_advanced ()
716 auto c = new CertificateChainEditor (
717 _panel, _("Signing DCPs and KDMs"), _border,
718 bind(&Config::set_signer_chain, Config::instance(), _1),
719 bind(&Config::signer_chain, Config::instance()),
727 KeysPage::export_decryption_chain_and_key ()
729 auto d = make_wx<wxFileDialog>(
730 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
731 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
734 if (d->ShowModal() != wxID_OK) {
738 boost::filesystem::path path(wx_to_std(d->GetPath()));
739 dcp::File f(path, "w");
741 throw OpenFileError(path, errno, OpenFileError::WRITE);
744 auto const chain = Config::instance()->decryption_chain()->chain();
745 f.checked_write(chain.c_str(), chain.length());
746 auto const key = Config::instance()->decryption_chain()->key();
747 DCPOMATIC_ASSERT(key);
748 f.checked_write(key->c_str(), key->length());
752 KeysPage::import_decryption_chain_and_key ()
754 if (NagDialog::maybe_nag (
756 Config::NAG_IMPORT_DECRYPTION_CHAIN,
757 _("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!"),
763 auto d = make_wx<wxFileDialog>(
764 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
767 if (d->ShowModal() != wxID_OK) {
771 auto new_chain = make_shared<dcp::CertificateChain>();
773 dcp::File f(wx_to_std(d->GetPath()), "r");
775 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
781 if (f.gets(buffer, 128) == 0) {
785 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
786 new_chain->add(dcp::Certificate(current));
788 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
789 new_chain->set_key(current);
794 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
795 Config::instance()->set_decryption_chain(new_chain);
797 error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s export file")));
802 KeysPage::nag_alter_decryption_chain ()
804 return NagDialog::maybe_nag (
806 Config::NAG_ALTER_DECRYPTION_CHAIN,
807 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created. Also, any KDMs that have been sent to you will become useless. Proceed with caution!"),
813 KeysPage::export_decryption_certificate ()
815 auto config = Config::instance();
816 wxString default_name = "dcpomatic";
817 if (!config->dcp_creator().empty()) {
818 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
820 if (!config->dcp_issuer().empty()) {
821 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
823 default_name += wxT("_kdm_decryption_cert.pem");
825 auto d = make_wx<wxFileDialog>(
826 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
827 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
830 if (d->ShowModal() != wxID_OK) {
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());
848 SoundPage::GetName () const
856 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
857 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
861 _sound = new CheckBox (_panel, _("Play sound via"));
862 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
863 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
864 _sound_output = new wxChoice (_panel, wxID_ANY);
865 s->Add (_sound_output, 0);
866 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
867 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
868 table->Add (s, wxGBPosition(r, 1));
871 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
872 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
873 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
876 _reset_to_default = new Button (_panel, _("Reset to default"));
877 table->Add (_reset_to_default, wxGBPosition(r, 1));
880 wxFont font = _sound_output_details->GetFont();
881 font.SetStyle (wxFONTSTYLE_ITALIC);
882 font.SetPointSize (font.GetPointSize() - 1);
883 _sound_output_details->SetFont (font);
885 RtAudio audio (DCPOMATIC_RTAUDIO_API);
886 #if (RTAUDIO_VERSION_MAJOR >= 6)
887 for (auto device_id: audio.getDeviceIds()) {
888 auto dev = audio.getDeviceInfo(device_id);
889 if (dev.outputChannels > 0) {
890 _sound_output->Append(std_to_wx(dev.name));
894 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
896 auto dev = audio.getDeviceInfo (i);
897 if (dev.probed && dev.outputChannels > 0) {
898 _sound_output->Append (std_to_wx (dev.name));
900 } catch (RtAudioError&) {
901 /* Something went wrong so let's just ignore that device */
906 _sound->bind(&SoundPage::sound_changed, this);
907 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
908 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
909 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
913 SoundPage::reset_to_default ()
915 Config::instance()->set_audio_mapping_to_default ();
919 SoundPage::map_changed (AudioMapping m)
921 Config::instance()->set_audio_mapping (m);
925 SoundPage::sound_changed ()
927 Config::instance()->set_sound (_sound->GetValue ());
931 SoundPage::sound_output_changed ()
933 RtAudio audio (DCPOMATIC_RTAUDIO_API);
934 auto const so = get_sound_output();
935 string default_device;
936 #if (RTAUDIO_VERSION_MAJOR >= 6)
937 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
940 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
941 } catch (RtAudioError&) {
945 if (!so || *so == default_device) {
946 Config::instance()->unset_sound_output ();
948 Config::instance()->set_sound_output (*so);
953 SoundPage::config_changed ()
955 auto config = Config::instance ();
957 checked_set (_sound, config->sound ());
959 auto const current_so = get_sound_output ();
960 optional<string> configured_so;
962 if (config->sound_output()) {
963 configured_so = config->sound_output().get();
965 /* No configured output means we should use the default */
966 RtAudio audio (DCPOMATIC_RTAUDIO_API);
967 #if (RTAUDIO_VERSION_MAJOR >= 6)
968 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
971 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
972 } catch (RtAudioError&) {
973 /* Probably no audio devices at all */
978 if (configured_so && current_so != configured_so) {
979 /* Update _sound_output with the configured value */
981 while (i < _sound_output->GetCount()) {
982 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
983 _sound_output->SetSelection (i);
990 RtAudio audio (DCPOMATIC_RTAUDIO_API);
992 map<int, wxString> apis;
993 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
994 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
995 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
996 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
997 apis[RtAudio::UNIX_JACK] = _("JACK");
998 apis[RtAudio::LINUX_ALSA] = _("ALSA");
999 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1000 apis[RtAudio::LINUX_OSS] = _("OSS");
1001 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1004 if (configured_so) {
1005 #if (RTAUDIO_VERSION_MAJOR >= 6)
1006 for (auto device_id: audio.getDeviceIds()) {
1007 auto info = audio.getDeviceInfo(device_id);
1008 if (info.name == *configured_so && info.outputChannels > 0) {
1009 channels = info.outputChannels;
1013 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1015 auto info = audio.getDeviceInfo(i);
1016 if (info.name == *configured_so && info.outputChannels > 0) {
1017 channels = info.outputChannels;
1019 } catch (RtAudioError&) {
1026 _sound_output_details->SetLabel (
1027 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1030 _map->set (Config::instance()->audio_mapping(channels));
1032 vector<NamedChannel> input;
1033 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1034 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1036 _map->set_input_channels (input);
1038 vector<NamedChannel> output;
1039 for (int i = 0; i < channels; ++i) {
1040 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1042 _map->set_output_channels (output);
1044 setup_sensitivity ();
1048 SoundPage::setup_sensitivity ()
1050 _sound_output->Enable (_sound->GetValue());
1053 /** @return Currently-selected preview sound output in the dialogue */
1055 SoundPage::get_sound_output ()
1057 int const sel = _sound_output->GetSelection ();
1058 if (sel == wxNOT_FOUND) {
1059 return optional<string> ();
1062 return wx_to_std (_sound_output->GetString (sel));
1066 LocationsPage::LocationsPage (wxSize panel_size, int border)
1067 : Page (panel_size, border)
1073 LocationsPage::GetName () const
1075 return _("Locations");
1078 #ifdef DCPOMATIC_OSX
1080 LocationsPage::GetLargeIcon () const
1082 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1087 LocationsPage::setup ()
1091 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1092 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1094 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1095 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1096 table->Add (_content_directory, wxGBPosition (r, 1));
1099 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1100 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1101 table->Add (_playlist_directory, wxGBPosition (r, 1));
1104 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1105 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1106 table->Add (_kdm_directory, wxGBPosition (r, 1));
1109 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1110 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1111 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1115 LocationsPage::config_changed ()
1117 auto config = Config::instance ();
1119 if (config->player_content_directory()) {
1120 checked_set (_content_directory, *config->player_content_directory());
1122 if (config->player_playlist_directory()) {
1123 checked_set (_playlist_directory, *config->player_playlist_directory());
1125 if (config->player_kdm_directory()) {
1126 checked_set (_kdm_directory, *config->player_kdm_directory());
1131 LocationsPage::content_directory_changed ()
1133 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1137 LocationsPage::playlist_directory_changed ()
1139 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1143 LocationsPage::kdm_directory_changed ()
1145 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));