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/>.
21 #include "config_dialog.h"
22 #include "static_text.h"
23 #include "check_box.h"
24 #include "nag_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "audio_mapping_view.h"
27 #include <dcp/raw_convert.h>
35 using std::make_shared;
37 using boost::optional;
38 using std::shared_ptr;
40 #if BOOST_VERSION >= 106100
41 using namespace boost::placeholders;
51 Page::Page (wxSize panel_size, int border)
54 , _panel_size (panel_size)
55 , _window_exists (false)
57 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
62 Page::CreateWindow (wxWindow* parent)
64 return create_window (parent);
69 Page::create_window (wxWindow* parent)
71 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
72 auto s = new wxBoxSizer (wxVERTICAL);
76 _window_exists = true;
79 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
85 Page::config_changed_wrapper ()
93 Page::window_destroyed ()
95 _window_exists = false;
99 GeneralPage::GeneralPage (wxSize panel_size, int border)
100 : Page (panel_size, border)
107 GeneralPage::GetName () const
114 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
116 _set_language = new CheckBox (_panel, _("Set language"));
117 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
118 _language = new wxChoice (_panel, wxID_ANY);
119 vector<pair<string, string>> languages;
120 languages.push_back (make_pair("Čeština", "cs_CZ"));
121 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
122 languages.push_back (make_pair("Dansk", "da_DK"));
123 languages.push_back (make_pair("Deutsch", "de_DE"));
124 languages.push_back (make_pair("English", "en_GB"));
125 languages.push_back (make_pair("Español", "es_ES"));
126 languages.push_back (make_pair("Français", "fr_FR"));
127 languages.push_back (make_pair("Italiano", "it_IT"));
128 languages.push_back (make_pair("Nederlands", "nl_NL"));
129 languages.push_back (make_pair("Русский", "ru_RU"));
130 languages.push_back (make_pair("Polski", "pl_PL"));
131 languages.push_back (make_pair("Português europeu", "pt_PT"));
132 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
133 languages.push_back (make_pair("Svenska", "sv_SE"));
134 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
135 // languages.push_back (make_pair("Türkçe", "tr_TR"));
136 languages.push_back (make_pair("українська мова", "uk_UA"));
137 checked_set (_language, languages);
138 table->Add (_language, wxGBPosition (r, 1));
141 auto restart = add_label_to_sizer (
142 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
144 wxFont font = restart->GetFont();
145 font.SetStyle (wxFONTSTYLE_ITALIC);
146 font.SetPointSize (font.GetPointSize() - 1);
147 restart->SetFont (font);
150 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
151 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
155 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
157 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
158 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
161 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
162 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
165 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
166 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
170 GeneralPage::config_changed ()
172 auto config = Config::instance ();
174 checked_set (_set_language, static_cast<bool>(config->language()));
176 /* Backwards compatibility of config file */
178 map<string, string> compat_map;
179 compat_map["fr"] = "fr_FR";
180 compat_map["it"] = "it_IT";
181 compat_map["es"] = "es_ES";
182 compat_map["sv"] = "sv_SE";
183 compat_map["de"] = "de_DE";
184 compat_map["nl"] = "nl_NL";
185 compat_map["ru"] = "ru_RU";
186 compat_map["pl"] = "pl_PL";
187 compat_map["da"] = "da_DK";
188 compat_map["pt"] = "pt_PT";
189 compat_map["sk"] = "sk_SK";
190 compat_map["cs"] = "cs_CZ";
191 compat_map["uk"] = "uk_UA";
193 auto lang = config->language().get_value_or("en_GB");
194 if (compat_map.find(lang) != compat_map.end ()) {
195 lang = compat_map[lang];
198 checked_set (_language, lang);
200 checked_set (_check_for_updates, config->check_for_updates ());
201 checked_set (_check_for_test_updates, config->check_for_test_updates ());
203 setup_sensitivity ();
207 GeneralPage::setup_sensitivity ()
209 _language->Enable (_set_language->GetValue ());
210 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
214 GeneralPage::set_language_changed ()
216 setup_sensitivity ();
217 if (_set_language->GetValue ()) {
220 Config::instance()->unset_language ();
225 GeneralPage::language_changed ()
227 int const sel = _language->GetSelection ();
229 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
231 Config::instance()->unset_language ();
236 GeneralPage::check_for_updates_changed ()
238 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
242 GeneralPage::check_for_test_updates_changed ()
244 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
247 CertificateChainEditor::CertificateChainEditor (
251 function<void (shared_ptr<dcp::CertificateChain>)> set,
252 function<shared_ptr<const dcp::CertificateChain> (void)> get,
253 function<bool (void)> nag_alter
255 : wxDialog (parent, wxID_ANY, title)
258 , _nag_alter (nag_alter)
260 _sizer = new wxBoxSizer (wxVERTICAL);
262 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
263 _sizer->Add (certificates_sizer, 0, wxALL, border);
265 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
270 ip.SetText (_("Type"));
272 _certificates->InsertColumn (0, ip);
278 ip.SetText (_("Thumbprint"));
281 wxFont font = ip.GetFont ();
282 font.SetFamily (wxFONTFAMILY_TELETYPE);
285 _certificates->InsertColumn (1, ip);
288 certificates_sizer->Add (_certificates, 1, wxEXPAND);
291 auto s = new wxBoxSizer (wxVERTICAL);
292 _add_certificate = new Button (this, _("Add..."));
293 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
294 _remove_certificate = new Button (this, _("Remove"));
295 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
296 _export_certificate = new Button (this, _("Export certificate..."));
297 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
298 _export_chain = new Button (this, _("Export chain..."));
299 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
303 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
304 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
307 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
308 _private_key = new StaticText (this, wxT(""));
309 wxFont font = _private_key->GetFont ();
310 font.SetFamily (wxFONTFAMILY_TELETYPE);
311 _private_key->SetFont (font);
312 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
313 _import_private_key = new Button (this, _("Import..."));
314 table->Add (_import_private_key, wxGBPosition (r, 2));
315 _export_private_key = new Button (this, _("Export..."));
316 table->Add (_export_private_key, wxGBPosition (r, 3));
319 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
320 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
321 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
322 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
325 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
326 font = *wxSMALL_FONT;
327 font.SetWeight (wxFONTWEIGHT_BOLD);
328 _private_key_bad->SetFont (font);
329 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
332 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
333 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
334 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
335 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
336 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
337 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
338 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
339 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
340 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
342 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
344 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
347 SetSizerAndFit (_sizer);
349 update_certificate_list ();
350 update_private_key ();
351 update_sensitivity ();
355 CertificateChainEditor::add_button (wxWindow* button)
357 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
362 CertificateChainEditor::add_certificate ()
364 auto d = new wxFileDialog (this, _("Select Certificate File"));
366 if (d->ShowModal() == wxID_OK) {
371 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
372 } catch (boost::filesystem::filesystem_error& e) {
373 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
378 if (!extra.empty ()) {
381 _("This file contains other certificates (or other data) after its first certificate. "
382 "Only the first certificate will be used.")
385 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
387 if (!chain->chain_valid ()) {
390 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
391 "Add certificates in order from root to intermediate to leaf.")
396 update_certificate_list ();
398 } catch (dcp::MiscError& e) {
399 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
405 update_sensitivity ();
409 CertificateChainEditor::remove_certificate ()
412 /* Cancel was clicked */
416 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
421 _certificates->DeleteItem (i);
422 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
426 update_sensitivity ();
427 update_certificate_list ();
431 CertificateChainEditor::export_certificate ()
433 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
438 auto all = _get()->root_to_leaf();
440 wxString default_name;
442 default_name = "root.pem";
443 } else if (i == static_cast<int>(all.size() - 1)) {
444 default_name = "leaf.pem";
446 default_name = "intermediate.pem";
449 auto d = new wxFileDialog(
450 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
451 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
454 auto j = all.begin ();
455 for (int k = 0; k < i; ++k) {
459 if (d->ShowModal () == wxID_OK) {
460 boost::filesystem::path path (wx_to_std(d->GetPath()));
461 if (path.extension() != ".pem") {
464 auto f = fopen_boost (path, "w");
466 throw OpenFileError (path, errno, OpenFileError::WRITE);
469 string const s = j->certificate (true);
470 checked_fwrite (s.c_str(), s.length(), f, path);
477 CertificateChainEditor::export_chain ()
479 auto d = new wxFileDialog (
480 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
481 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
484 if (d->ShowModal () == wxID_OK) {
485 boost::filesystem::path path (wx_to_std(d->GetPath()));
486 if (path.extension() != ".pem") {
489 auto f = fopen_boost (path, "w");
491 throw OpenFileError (path, errno, OpenFileError::WRITE);
494 auto const s = _get()->chain();
495 checked_fwrite (s.c_str(), s.length(), f, path);
503 CertificateChainEditor::update_certificate_list ()
505 _certificates->DeleteAllItems ();
507 auto certs = _get()->root_to_leaf();
508 for (auto const& i: certs) {
511 _certificates->InsertItem (item);
512 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
515 _certificates->SetItem (n, 0, _("Root"));
516 } else if (n == (certs.size() - 1)) {
517 _certificates->SetItem (n, 0, _("Leaf"));
519 _certificates->SetItem (n, 0, _("Intermediate"));
525 static wxColour normal = _private_key_bad->GetForegroundColour ();
527 if (_get()->private_key_valid()) {
528 _private_key_bad->Hide ();
529 _private_key_bad->SetForegroundColour (normal);
531 _private_key_bad->Show ();
532 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
537 CertificateChainEditor::remake_certificates ()
540 /* Cancel was clicked */
544 auto d = new MakeChainDialog (this, _get());
546 if (d->ShowModal () == wxID_OK) {
548 update_certificate_list ();
549 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 = new 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()));
597 update_sensitivity ();
601 CertificateChainEditor::export_private_key ()
603 auto key = _get()->key();
608 auto d = new 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 auto f = fopen_boost (path, "w");
620 throw OpenFileError (path, errno, OpenFileError::WRITE);
623 auto const s = _get()->key().get ();
624 checked_fwrite (s.c_str(), s.length(), f, path);
631 KeysPage::GetName () const
639 wxFont subheading_font (*wxNORMAL_FONT);
640 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
642 auto sizer = _panel->GetSizer();
645 auto m = new StaticText (_panel, _("Decrypting KDMs"));
646 m->SetFont (subheading_font);
647 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
650 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
652 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
653 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
655 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
657 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
658 auto decryption_advanced = new Button (_panel, _("Advanced..."));
659 kdm_buttons->Add (decryption_advanced, 0);
661 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
663 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
664 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
665 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
666 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
669 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
670 m->SetFont (subheading_font);
671 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
674 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
676 auto signing_advanced = new Button (_panel, _("Advanced..."));
677 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
678 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
679 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
681 sizer->Add (signing_buttons, 0, wxLEFT, _border);
683 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
684 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
689 KeysPage::remake_signing ()
691 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
693 if (d->ShowModal () == wxID_OK) {
694 Config::instance()->set_signer_chain(d->get());
700 KeysPage::decryption_advanced ()
702 auto c = new CertificateChainEditor (
703 _panel, _("Decrypting KDMs"), _border,
704 bind(&Config::set_decryption_chain, Config::instance(), _1),
705 bind(&Config::decryption_chain, Config::instance()),
706 bind(&KeysPage::nag_alter_decryption_chain, this)
713 KeysPage::signing_advanced ()
715 auto c = new CertificateChainEditor (
716 _panel, _("Signing DCPs and KDMs"), _border,
717 bind(&Config::set_signer_chain, Config::instance(), _1),
718 bind(&Config::signer_chain, Config::instance()),
726 KeysPage::export_decryption_chain_and_key ()
728 auto d = new wxFileDialog (
729 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
730 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
733 if (d->ShowModal () == wxID_OK) {
734 boost::filesystem::path path (wx_to_std(d->GetPath()));
735 auto f = fopen_boost (path, "w");
737 throw OpenFileError (path, errno, OpenFileError::WRITE);
740 auto const chain = Config::instance()->decryption_chain()->chain();
741 checked_fwrite (chain.c_str(), chain.length(), f, path);
742 optional<string> const key = Config::instance()->decryption_chain()->key();
743 DCPOMATIC_ASSERT (key);
744 checked_fwrite (key->c_str(), key->length(), f, path);
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 = new wxFileDialog (
764 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
767 if (d->ShowModal () == wxID_OK) {
768 auto new_chain = make_shared<dcp::CertificateChain>();
770 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
772 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
778 if (fgets (buffer, 128, f) == 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);
792 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
793 Config::instance()->set_decryption_chain (new_chain);
795 error_dialog (_panel, _("Invalid DCP-o-matic 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 = new 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) {
831 boost::filesystem::path path (wx_to_std(d->GetPath()));
832 if (path.extension() != ".pem") {
835 auto f = fopen_boost (path, "w");
837 throw OpenFileError (path, errno, OpenFileError::WRITE);
840 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
841 checked_fwrite (s.c_str(), s.length(), f, path);
849 SoundPage::GetName () const
857 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
858 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
862 _sound = new CheckBox (_panel, _("Play sound via"));
863 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
864 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
865 _sound_output = new wxChoice (_panel, wxID_ANY);
866 s->Add (_sound_output, 0);
867 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
868 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
869 table->Add (s, wxGBPosition(r, 1));
872 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
873 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
874 _map->SetSize (-1, 400);
875 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
878 _reset_to_default = new Button (_panel, _("Reset to default"));
879 table->Add (_reset_to_default, wxGBPosition(r, 1));
882 wxFont font = _sound_output_details->GetFont();
883 font.SetStyle (wxFONTSTYLE_ITALIC);
884 font.SetPointSize (font.GetPointSize() - 1);
885 _sound_output_details->SetFont (font);
887 RtAudio audio (DCPOMATIC_RTAUDIO_API);
888 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
890 auto dev = audio.getDeviceInfo (i);
891 if (dev.probed && dev.outputChannels > 0) {
892 _sound_output->Append (std_to_wx (dev.name));
894 } catch (RtAudioError&) {
895 /* Something went wrong so let's just ignore that device */
899 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
900 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
901 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
902 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
906 SoundPage::reset_to_default ()
908 Config::instance()->set_audio_mapping_to_default ();
912 SoundPage::map_changed (AudioMapping m)
914 Config::instance()->set_audio_mapping (m);
918 SoundPage::sound_changed ()
920 Config::instance()->set_sound (_sound->GetValue ());
924 SoundPage::sound_output_changed ()
926 RtAudio audio (DCPOMATIC_RTAUDIO_API);
927 auto const so = get_sound_output();
928 string default_device;
930 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
931 } catch (RtAudioError&) {
934 if (!so || *so == default_device) {
935 Config::instance()->unset_sound_output ();
937 Config::instance()->set_sound_output (*so);
942 SoundPage::config_changed ()
944 auto config = Config::instance ();
946 checked_set (_sound, config->sound ());
948 auto const current_so = get_sound_output ();
949 optional<string> configured_so;
951 if (config->sound_output()) {
952 configured_so = config->sound_output().get();
954 /* No configured output means we should use the default */
955 RtAudio audio (DCPOMATIC_RTAUDIO_API);
957 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
958 } catch (RtAudioError&) {
959 /* Probably no audio devices at all */
963 if (configured_so && current_so != configured_so) {
964 /* Update _sound_output with the configured value */
966 while (i < _sound_output->GetCount()) {
967 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
968 _sound_output->SetSelection (i);
975 RtAudio audio (DCPOMATIC_RTAUDIO_API);
977 map<int, wxString> apis;
978 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
979 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
980 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
981 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
982 apis[RtAudio::UNIX_JACK] = _("JACK");
983 apis[RtAudio::LINUX_ALSA] = _("ALSA");
984 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
985 apis[RtAudio::LINUX_OSS] = _("OSS");
986 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
990 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
992 auto info = audio.getDeviceInfo(i);
993 if (info.name == *configured_so && info.outputChannels > 0) {
994 channels = info.outputChannels;
996 } catch (RtAudioError&) {
1002 _sound_output_details->SetLabel (
1003 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1006 _map->set (Config::instance()->audio_mapping(channels));
1008 vector<NamedChannel> input;
1009 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1010 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1012 _map->set_input_channels (input);
1014 vector<NamedChannel> output;
1015 for (int i = 0; i < channels; ++i) {
1016 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1018 _map->set_output_channels (output);
1020 setup_sensitivity ();
1024 SoundPage::setup_sensitivity ()
1026 _sound_output->Enable (_sound->GetValue());
1029 /** @return Currently-selected preview sound output in the dialogue */
1031 SoundPage::get_sound_output ()
1033 int const sel = _sound_output->GetSelection ();
1034 if (sel == wxNOT_FOUND) {
1035 return optional<string> ();
1038 return wx_to_std (_sound_output->GetString (sel));
1042 LocationsPage::LocationsPage (wxSize panel_size, int border)
1043 : Page (panel_size, border)
1049 LocationsPage::GetName () const
1051 return _("Locations");
1054 #ifdef DCPOMATIC_OSX
1056 LocationsPage::GetLargeIcon () const
1058 return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
1063 LocationsPage::setup ()
1067 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1068 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1070 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1071 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1072 table->Add (_content_directory, wxGBPosition (r, 1));
1075 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1076 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1077 table->Add (_playlist_directory, wxGBPosition (r, 1));
1080 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1081 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1082 table->Add (_kdm_directory, wxGBPosition (r, 1));
1085 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1086 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1087 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1091 LocationsPage::config_changed ()
1093 auto config = Config::instance ();
1095 if (config->player_content_directory()) {
1096 checked_set (_content_directory, *config->player_content_directory());
1098 if (config->player_playlist_directory()) {
1099 checked_set (_playlist_directory, *config->player_playlist_directory());
1101 if (config->player_kdm_directory()) {
1102 checked_set (_kdm_directory, *config->player_kdm_directory());
1107 LocationsPage::content_directory_changed ()
1109 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1113 LocationsPage::playlist_directory_changed ()
1115 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1119 LocationsPage::kdm_directory_changed ()
1121 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));