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/raw_convert.h>
35 using std::make_shared;
38 using std::shared_ptr;
42 using boost::optional;
43 #if BOOST_VERSION >= 106100
44 using namespace boost::placeholders;
55 Page::Page (wxSize panel_size, int border)
58 , _panel_size (panel_size)
59 , _window_exists (false)
61 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
66 Page::CreateWindow (wxWindow* parent)
68 return create_window (parent);
73 Page::create_window (wxWindow* parent)
75 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
76 auto s = new wxBoxSizer (wxVERTICAL);
80 _window_exists = true;
83 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
89 Page::config_changed_wrapper ()
97 Page::window_destroyed ()
99 _window_exists = false;
103 GeneralPage::GeneralPage (wxSize panel_size, int border)
104 : Page (panel_size, border)
111 GeneralPage::GetName () const
118 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
120 _set_language = new CheckBox (_panel, _("Set language"));
121 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
122 _language = new wxChoice (_panel, wxID_ANY);
123 vector<pair<string, string>> languages;
124 languages.push_back (make_pair("Čeština", "cs_CZ"));
125 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
126 languages.push_back (make_pair("Dansk", "da_DK"));
127 languages.push_back (make_pair("Deutsch", "de_DE"));
128 languages.push_back (make_pair("English", "en_GB"));
129 languages.push_back (make_pair("Español", "es_ES"));
130 languages.push_back (make_pair("Français", "fr_FR"));
131 languages.push_back (make_pair("Italiano", "it_IT"));
132 languages.push_back (make_pair("Nederlands", "nl_NL"));
133 languages.push_back (make_pair("Русский", "ru_RU"));
134 languages.push_back (make_pair("Polski", "pl_PL"));
135 languages.push_back (make_pair("Português europeu", "pt_PT"));
136 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
137 languages.push_back (make_pair("Svenska", "sv_SE"));
138 languages.push_back (make_pair("Slovenščina", "sl_SI"));
139 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
140 // languages.push_back (make_pair("Türkçe", "tr_TR"));
141 languages.push_back (make_pair("українська мова", "uk_UA"));
142 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
143 checked_set (_language, languages);
144 table->Add (_language, wxGBPosition (r, 1));
147 auto restart = add_label_to_sizer (
148 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
150 wxFont font = restart->GetFont();
151 font.SetStyle (wxFONTSTYLE_ITALIC);
152 font.SetPointSize (font.GetPointSize() - 1);
153 restart->SetFont (font);
156 _set_language->bind(&GeneralPage::set_language_changed, this);
157 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
161 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
163 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
164 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
167 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
168 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
171 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
172 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
176 GeneralPage::config_changed ()
178 auto config = Config::instance ();
180 checked_set (_set_language, static_cast<bool>(config->language()));
182 /* Backwards compatibility of config file */
184 map<string, string> compat_map;
185 compat_map["fr"] = "fr_FR";
186 compat_map["it"] = "it_IT";
187 compat_map["es"] = "es_ES";
188 compat_map["sv"] = "sv_SE";
189 compat_map["de"] = "de_DE";
190 compat_map["nl"] = "nl_NL";
191 compat_map["ru"] = "ru_RU";
192 compat_map["pl"] = "pl_PL";
193 compat_map["da"] = "da_DK";
194 compat_map["pt"] = "pt_PT";
195 compat_map["sk"] = "sk_SK";
196 compat_map["cs"] = "cs_CZ";
197 compat_map["uk"] = "uk_UA";
199 auto lang = config->language().get_value_or("en_GB");
200 if (compat_map.find(lang) != compat_map.end ()) {
201 lang = compat_map[lang];
204 checked_set (_language, lang);
206 checked_set (_check_for_updates, config->check_for_updates ());
207 checked_set (_check_for_test_updates, config->check_for_test_updates ());
209 setup_sensitivity ();
213 GeneralPage::setup_sensitivity ()
215 _language->Enable (_set_language->GetValue ());
216 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
220 GeneralPage::set_language_changed ()
222 setup_sensitivity ();
223 if (_set_language->GetValue ()) {
226 Config::instance()->unset_language ();
231 GeneralPage::language_changed ()
233 int const sel = _language->GetSelection ();
235 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
237 Config::instance()->unset_language ();
242 GeneralPage::check_for_updates_changed ()
244 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
248 GeneralPage::check_for_test_updates_changed ()
250 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
253 CertificateChainEditor::CertificateChainEditor (
257 function<void (shared_ptr<dcp::CertificateChain>)> set,
258 function<shared_ptr<const dcp::CertificateChain> (void)> get,
259 function<bool (void)> nag_alter
261 : wxDialog (parent, wxID_ANY, title)
264 , _nag_alter (nag_alter)
266 _sizer = new wxBoxSizer (wxVERTICAL);
268 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
269 _sizer->Add (certificates_sizer, 0, wxALL, border);
271 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
276 ip.SetText (_("Type"));
278 _certificates->InsertColumn (0, ip);
284 ip.SetText (_("Thumbprint"));
287 wxFont font = ip.GetFont ();
288 font.SetFamily (wxFONTFAMILY_TELETYPE);
291 _certificates->InsertColumn (1, ip);
294 certificates_sizer->Add (_certificates, 1, wxEXPAND);
297 auto s = new wxBoxSizer (wxVERTICAL);
298 _add_certificate = new Button (this, _("Add..."));
299 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 _remove_certificate = new Button (this, _("Remove"));
301 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
302 _export_certificate = new Button (this, _("Export certificate..."));
303 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304 _export_chain = new Button (this, _("Export chain..."));
305 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
306 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
309 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
310 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
313 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
314 _private_key = new StaticText (this, wxT(""));
315 wxFont font = _private_key->GetFont ();
316 font.SetFamily (wxFONTFAMILY_TELETYPE);
317 _private_key->SetFont (font);
318 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
319 _import_private_key = new Button (this, _("Import..."));
320 table->Add (_import_private_key, wxGBPosition (r, 2));
321 _export_private_key = new Button (this, _("Export..."));
322 table->Add (_export_private_key, wxGBPosition (r, 3));
325 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
326 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
327 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
328 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
331 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
332 font = *wxSMALL_FONT;
333 font.SetWeight (wxFONTWEIGHT_BOLD);
334 _private_key_bad->SetFont (font);
335 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
338 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
339 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
340 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
341 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
342 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
343 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
344 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
345 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
346 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
348 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
350 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
353 SetSizerAndFit (_sizer);
355 update_certificate_list ();
356 update_private_key ();
357 update_sensitivity ();
361 CertificateChainEditor::add_button (wxWindow* button)
363 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
368 CertificateChainEditor::add_certificate ()
370 auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
372 if (d->ShowModal() == wxID_OK) {
377 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
378 } catch (boost::filesystem::filesystem_error& e) {
379 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
383 if (!extra.empty ()) {
386 _("This file contains other certificates (or other data) after its first certificate. "
387 "Only the first certificate will be used.")
390 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
392 if (!chain->chain_valid ()) {
395 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
396 "Add certificates in order from root to intermediate to leaf.")
401 update_certificate_list ();
403 } catch (dcp::MiscError& e) {
404 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
408 update_sensitivity ();
412 CertificateChainEditor::remove_certificate ()
415 /* Cancel was clicked */
419 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
424 _certificates->DeleteItem (i);
425 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
429 update_sensitivity ();
430 update_certificate_list ();
434 CertificateChainEditor::export_certificate ()
436 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
441 auto all = _get()->root_to_leaf();
443 wxString default_name;
445 default_name = "root.pem";
446 } else if (i == static_cast<int>(all.size() - 1)) {
447 default_name = "leaf.pem";
449 default_name = "intermediate.pem";
452 auto d = make_wx<wxFileDialog>(
453 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
454 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
457 auto j = all.begin ();
458 for (int k = 0; k < i; ++k) {
462 if (d->ShowModal() != wxID_OK) {
466 boost::filesystem::path path(wx_to_std(d->GetPath()));
467 if (path.extension() != ".pem") {
470 dcp::File f(path, "w");
472 throw OpenFileError(path, errno, OpenFileError::WRITE);
475 string const s = j->certificate(true);
476 f.checked_write(s.c_str(), s.length());
480 CertificateChainEditor::export_chain ()
482 auto d = make_wx<wxFileDialog>(
483 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
484 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
487 if (d->ShowModal() != wxID_OK) {
491 boost::filesystem::path path(wx_to_std(d->GetPath()));
492 if (path.extension() != ".pem") {
495 dcp::File f(path, "w");
497 throw OpenFileError(path, errno, OpenFileError::WRITE);
500 auto const s = _get()->chain();
501 f.checked_write(s.c_str(), s.length());
505 CertificateChainEditor::update_certificate_list ()
507 _certificates->DeleteAllItems ();
509 auto certs = _get()->root_to_leaf();
510 for (auto const& i: certs) {
513 _certificates->InsertItem (item);
514 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
517 _certificates->SetItem (n, 0, _("Root"));
518 } else if (n == (certs.size() - 1)) {
519 _certificates->SetItem (n, 0, _("Leaf"));
521 _certificates->SetItem (n, 0, _("Intermediate"));
527 static wxColour normal = _private_key_bad->GetForegroundColour ();
529 if (_get()->private_key_valid()) {
530 _private_key_bad->Hide ();
531 _private_key_bad->SetForegroundColour (normal);
533 _private_key_bad->Show ();
534 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
539 CertificateChainEditor::remake_certificates ()
542 /* Cancel was clicked */
546 auto d = make_wx<MakeChainDialog>(this, _get());
548 if (d->ShowModal () == wxID_OK) {
550 update_certificate_list ();
551 update_private_key ();
556 CertificateChainEditor::update_sensitivity ()
558 /* We can only remove the leaf certificate */
559 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
560 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
564 CertificateChainEditor::update_private_key ()
566 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
571 CertificateChainEditor::import_private_key ()
573 auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
575 if (d->ShowModal() == wxID_OK) {
577 boost::filesystem::path p (wx_to_std (d->GetPath ()));
578 if (boost::filesystem::file_size (p) > 8192) {
581 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
586 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
587 chain->set_key (dcp::file_to_string (p));
589 update_private_key ();
590 } catch (std::exception& e) {
591 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
595 update_sensitivity ();
599 CertificateChainEditor::export_private_key ()
601 auto key = _get()->key();
606 auto d = make_wx<wxFileDialog>(
607 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
608 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
611 if (d->ShowModal () == wxID_OK) {
612 boost::filesystem::path path (wx_to_std(d->GetPath()));
613 if (path.extension() != ".pem") {
616 dcp::File f(path, "w");
618 throw OpenFileError (path, errno, OpenFileError::WRITE);
621 auto const s = _get()->key().get ();
622 f.checked_write(s.c_str(), s.length());
627 KeysPage::GetName () const
635 wxFont subheading_font (*wxNORMAL_FONT);
636 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
638 auto sizer = _panel->GetSizer();
641 auto m = new StaticText (_panel, _("Decrypting KDMs"));
642 m->SetFont (subheading_font);
643 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
646 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
648 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
649 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
650 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
651 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
652 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
653 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654 auto decryption_advanced = new Button (_panel, _("Advanced..."));
655 kdm_buttons->Add (decryption_advanced, 0);
657 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
659 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
660 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
661 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
662 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
665 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
666 m->SetFont (subheading_font);
667 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
670 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
672 auto signing_advanced = new Button (_panel, _("Advanced..."));
673 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
674 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
675 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
677 sizer->Add (signing_buttons, 0, wxLEFT, _border);
679 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
680 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
685 KeysPage::remake_signing ()
687 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
689 if (d->ShowModal () == wxID_OK) {
690 Config::instance()->set_signer_chain(d->get());
696 KeysPage::decryption_advanced ()
698 auto c = new CertificateChainEditor (
699 _panel, _("Decrypting KDMs"), _border,
700 bind(&Config::set_decryption_chain, Config::instance(), _1),
701 bind(&Config::decryption_chain, Config::instance()),
702 bind(&KeysPage::nag_alter_decryption_chain, this)
709 KeysPage::signing_advanced ()
711 auto c = new CertificateChainEditor (
712 _panel, _("Signing DCPs and KDMs"), _border,
713 bind(&Config::set_signer_chain, Config::instance(), _1),
714 bind(&Config::signer_chain, Config::instance()),
722 KeysPage::export_decryption_chain_and_key ()
724 auto d = make_wx<wxFileDialog>(
725 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
726 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
729 if (d->ShowModal() != wxID_OK) {
733 boost::filesystem::path path(wx_to_std(d->GetPath()));
734 dcp::File f(path, "w");
736 throw OpenFileError(path, errno, OpenFileError::WRITE);
739 auto const chain = Config::instance()->decryption_chain()->chain();
740 f.checked_write(chain.c_str(), chain.length());
741 auto const key = Config::instance()->decryption_chain()->key();
742 DCPOMATIC_ASSERT(key);
743 f.checked_write(key->c_str(), key->length());
747 KeysPage::import_decryption_chain_and_key ()
749 if (NagDialog::maybe_nag (
751 Config::NAG_IMPORT_DECRYPTION_CHAIN,
752 _("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!"),
758 auto d = make_wx<wxFileDialog>(
759 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
762 if (d->ShowModal() != wxID_OK) {
766 auto new_chain = make_shared<dcp::CertificateChain>();
768 dcp::File f(wx_to_std(d->GetPath()), "r");
770 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
776 if (f.gets(buffer, 128) == 0) {
780 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
781 new_chain->add(dcp::Certificate(current));
783 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
784 new_chain->set_key(current);
789 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
790 Config::instance()->set_decryption_chain(new_chain);
792 error_dialog(_panel, _("Invalid DCP-o-matic export file"));
797 KeysPage::nag_alter_decryption_chain ()
799 return NagDialog::maybe_nag (
801 Config::NAG_ALTER_DECRYPTION_CHAIN,
802 _("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!"),
808 KeysPage::export_decryption_certificate ()
810 auto config = Config::instance();
811 wxString default_name = "dcpomatic";
812 if (!config->dcp_creator().empty()) {
813 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
815 if (!config->dcp_issuer().empty()) {
816 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
818 default_name += wxT("_kdm_decryption_cert.pem");
820 auto d = make_wx<wxFileDialog>(
821 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
822 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
825 if (d->ShowModal() != wxID_OK) {
829 boost::filesystem::path path(wx_to_std(d->GetPath()));
830 if (path.extension() != ".pem") {
833 dcp::File f(path, "w");
835 throw OpenFileError(path, errno, OpenFileError::WRITE);
838 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
839 f.checked_write(s.c_str(), s.length());
843 SoundPage::GetName () const
851 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
852 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
856 _sound = new CheckBox (_panel, _("Play sound via"));
857 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
858 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
859 _sound_output = new wxChoice (_panel, wxID_ANY);
860 s->Add (_sound_output, 0);
861 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
862 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
863 table->Add (s, wxGBPosition(r, 1));
866 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
867 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
868 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
871 _reset_to_default = new Button (_panel, _("Reset to default"));
872 table->Add (_reset_to_default, wxGBPosition(r, 1));
875 wxFont font = _sound_output_details->GetFont();
876 font.SetStyle (wxFONTSTYLE_ITALIC);
877 font.SetPointSize (font.GetPointSize() - 1);
878 _sound_output_details->SetFont (font);
880 RtAudio audio (DCPOMATIC_RTAUDIO_API);
881 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
883 auto dev = audio.getDeviceInfo (i);
884 if (dev.probed && dev.outputChannels > 0) {
885 _sound_output->Append (std_to_wx (dev.name));
887 } catch (RtAudioError&) {
888 /* Something went wrong so let's just ignore that device */
892 _sound->bind(&SoundPage::sound_changed, this);
893 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
894 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
895 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
899 SoundPage::reset_to_default ()
901 Config::instance()->set_audio_mapping_to_default ();
905 SoundPage::map_changed (AudioMapping m)
907 Config::instance()->set_audio_mapping (m);
911 SoundPage::sound_changed ()
913 Config::instance()->set_sound (_sound->GetValue ());
917 SoundPage::sound_output_changed ()
919 RtAudio audio (DCPOMATIC_RTAUDIO_API);
920 auto const so = get_sound_output();
921 string default_device;
923 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
924 } catch (RtAudioError&) {
927 if (!so || *so == default_device) {
928 Config::instance()->unset_sound_output ();
930 Config::instance()->set_sound_output (*so);
935 SoundPage::config_changed ()
937 auto config = Config::instance ();
939 checked_set (_sound, config->sound ());
941 auto const current_so = get_sound_output ();
942 optional<string> configured_so;
944 if (config->sound_output()) {
945 configured_so = config->sound_output().get();
947 /* No configured output means we should use the default */
948 RtAudio audio (DCPOMATIC_RTAUDIO_API);
950 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
951 } catch (RtAudioError&) {
952 /* Probably no audio devices at all */
956 if (configured_so && current_so != configured_so) {
957 /* Update _sound_output with the configured value */
959 while (i < _sound_output->GetCount()) {
960 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
961 _sound_output->SetSelection (i);
968 RtAudio audio (DCPOMATIC_RTAUDIO_API);
970 map<int, wxString> apis;
971 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
972 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
973 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
974 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
975 apis[RtAudio::UNIX_JACK] = _("JACK");
976 apis[RtAudio::LINUX_ALSA] = _("ALSA");
977 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
978 apis[RtAudio::LINUX_OSS] = _("OSS");
979 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
983 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
985 auto info = audio.getDeviceInfo(i);
986 if (info.name == *configured_so && info.outputChannels > 0) {
987 channels = info.outputChannels;
989 } catch (RtAudioError&) {
995 _sound_output_details->SetLabel (
996 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
999 _map->set (Config::instance()->audio_mapping(channels));
1001 vector<NamedChannel> input;
1002 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1003 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1005 _map->set_input_channels (input);
1007 vector<NamedChannel> output;
1008 for (int i = 0; i < channels; ++i) {
1009 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1011 _map->set_output_channels (output);
1013 setup_sensitivity ();
1017 SoundPage::setup_sensitivity ()
1019 _sound_output->Enable (_sound->GetValue());
1022 /** @return Currently-selected preview sound output in the dialogue */
1024 SoundPage::get_sound_output ()
1026 int const sel = _sound_output->GetSelection ();
1027 if (sel == wxNOT_FOUND) {
1028 return optional<string> ();
1031 return wx_to_std (_sound_output->GetString (sel));
1035 LocationsPage::LocationsPage (wxSize panel_size, int border)
1036 : Page (panel_size, border)
1042 LocationsPage::GetName () const
1044 return _("Locations");
1047 #ifdef DCPOMATIC_OSX
1049 LocationsPage::GetLargeIcon () const
1051 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1056 LocationsPage::setup ()
1060 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1061 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1063 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1064 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1065 table->Add (_content_directory, wxGBPosition (r, 1));
1068 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1069 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1070 table->Add (_playlist_directory, wxGBPosition (r, 1));
1073 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1074 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1075 table->Add (_kdm_directory, wxGBPosition (r, 1));
1078 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1079 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1080 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1084 LocationsPage::config_changed ()
1086 auto config = Config::instance ();
1088 if (config->player_content_directory()) {
1089 checked_set (_content_directory, *config->player_content_directory());
1091 if (config->player_playlist_directory()) {
1092 checked_set (_playlist_directory, *config->player_playlist_directory());
1094 if (config->player_kdm_directory()) {
1095 checked_set (_kdm_directory, *config->player_kdm_directory());
1100 LocationsPage::content_directory_changed ()
1102 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1106 LocationsPage::playlist_directory_changed ()
1108 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1112 LocationsPage::kdm_directory_changed ()
1114 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));