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 ()
541 string subject_organization_name;
542 string subject_organizational_unit_name;
543 string root_common_name;
544 string intermediate_common_name;
545 string leaf_common_name;
547 auto all = chain->root_to_leaf ();
549 if (all.size() >= 1) {
551 subject_organization_name = chain->root().subject_organization_name ();
552 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
553 root_common_name = chain->root().subject_common_name ();
556 if (all.size() >= 2) {
558 leaf_common_name = chain->leaf().subject_common_name ();
561 if (all.size() >= 3) {
562 /* Have an intermediate */
563 dcp::CertificateChain::List::iterator i = all.begin ();
565 intermediate_common_name = i->subject_common_name ();
569 /* Cancel was clicked */
573 auto d = new MakeChainDialog (
575 subject_organization_name,
576 subject_organizational_unit_name,
578 intermediate_common_name,
582 if (d->ShowModal () == wxID_OK) {
584 make_shared<dcp::CertificateChain> (
587 d->organisational_unit (),
588 d->root_common_name (),
589 d->intermediate_common_name (),
590 d->leaf_common_name ()
594 update_certificate_list ();
595 update_private_key ();
602 CertificateChainEditor::update_sensitivity ()
604 /* We can only remove the leaf certificate */
605 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
606 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
610 CertificateChainEditor::update_private_key ()
612 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
617 CertificateChainEditor::import_private_key ()
619 auto d = new wxFileDialog (this, _("Select Key File"));
621 if (d->ShowModal() == wxID_OK) {
623 boost::filesystem::path p (wx_to_std (d->GetPath ()));
624 if (boost::filesystem::file_size (p) > 8192) {
627 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
632 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
633 chain->set_key (dcp::file_to_string (p));
635 update_private_key ();
636 } catch (std::exception& e) {
637 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
643 update_sensitivity ();
647 CertificateChainEditor::export_private_key ()
649 auto key = _get()->key();
654 auto d = new wxFileDialog (
655 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
656 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
659 if (d->ShowModal () == wxID_OK) {
660 boost::filesystem::path path (wx_to_std(d->GetPath()));
661 if (path.extension() != ".pem") {
664 auto f = fopen_boost (path, "w");
666 throw OpenFileError (path, errno, OpenFileError::WRITE);
669 auto const s = _get()->key().get ();
670 checked_fwrite (s.c_str(), s.length(), f, path);
677 KeysPage::GetName () const
685 wxFont subheading_font (*wxNORMAL_FONT);
686 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
688 auto sizer = _panel->GetSizer();
691 auto m = new StaticText (_panel, _("Decrypting KDMs"));
692 m->SetFont (subheading_font);
693 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
696 auto buttons = new wxBoxSizer (wxVERTICAL);
698 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
699 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
700 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
701 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
702 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
703 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
704 auto decryption_advanced = new Button (_panel, _("Advanced..."));
705 buttons->Add (decryption_advanced, 0);
707 sizer->Add (buttons, 0, wxLEFT, _border);
709 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
710 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
711 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
712 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
715 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
716 m->SetFont (subheading_font);
717 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
720 auto signing_advanced = new Button (_panel, _("Advanced..."));
721 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
722 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
726 KeysPage::decryption_advanced ()
728 auto c = new CertificateChainEditor (
729 _panel, _("Decrypting KDMs"), _border,
730 bind(&Config::set_decryption_chain, Config::instance(), _1),
731 bind(&Config::decryption_chain, Config::instance()),
732 bind(&KeysPage::nag_alter_decryption_chain, this)
739 KeysPage::signing_advanced ()
741 auto c = new CertificateChainEditor (
742 _panel, _("Signing DCPs and KDMs"), _border,
743 bind(&Config::set_signer_chain, Config::instance(), _1),
744 bind(&Config::signer_chain, Config::instance()),
752 KeysPage::export_decryption_chain_and_key ()
754 auto d = new wxFileDialog (
755 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
756 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
759 if (d->ShowModal () == wxID_OK) {
760 boost::filesystem::path path (wx_to_std(d->GetPath()));
761 auto f = fopen_boost (path, "w");
763 throw OpenFileError (path, errno, OpenFileError::WRITE);
766 auto const chain = Config::instance()->decryption_chain()->chain();
767 checked_fwrite (chain.c_str(), chain.length(), f, path);
768 optional<string> const key = Config::instance()->decryption_chain()->key();
769 DCPOMATIC_ASSERT (key);
770 checked_fwrite (key->c_str(), key->length(), f, path);
778 KeysPage::import_decryption_chain_and_key ()
780 if (NagDialog::maybe_nag (
782 Config::NAG_IMPORT_DECRYPTION_CHAIN,
783 _("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!"),
789 auto d = new wxFileDialog (
790 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
793 if (d->ShowModal () == wxID_OK) {
794 auto new_chain = make_shared<dcp::CertificateChain>();
796 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
798 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
804 if (fgets (buffer, 128, f) == 0) {
808 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
809 new_chain->add (dcp::Certificate (current));
811 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
812 new_chain->set_key (current);
818 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
819 Config::instance()->set_decryption_chain (new_chain);
821 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
828 KeysPage::nag_alter_decryption_chain ()
830 return NagDialog::maybe_nag (
832 Config::NAG_ALTER_DECRYPTION_CHAIN,
833 _("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!"),
839 KeysPage::export_decryption_certificate ()
841 auto config = Config::instance();
842 wxString default_name = "dcpomatic";
843 if (!config->dcp_creator().empty()) {
844 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
846 if (!config->dcp_issuer().empty()) {
847 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
849 default_name += wxT("_kdm_decryption_cert.pem");
851 auto d = new wxFileDialog (
852 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
853 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
856 if (d->ShowModal () == wxID_OK) {
857 boost::filesystem::path path (wx_to_std(d->GetPath()));
858 if (path.extension() != ".pem") {
861 auto f = fopen_boost (path, "w");
863 throw OpenFileError (path, errno, OpenFileError::WRITE);
866 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
867 checked_fwrite (s.c_str(), s.length(), f, path);
875 SoundPage::GetName () const
883 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
884 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
888 _sound = new CheckBox (_panel, _("Play sound via"));
889 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
890 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
891 _sound_output = new wxChoice (_panel, wxID_ANY);
892 s->Add (_sound_output, 0);
893 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
894 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
895 table->Add (s, wxGBPosition(r, 1));
898 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
899 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
900 _map->SetSize (-1, 400);
901 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
904 _reset_to_default = new Button (_panel, _("Reset to default"));
905 table->Add (_reset_to_default, wxGBPosition(r, 1));
908 wxFont font = _sound_output_details->GetFont();
909 font.SetStyle (wxFONTSTYLE_ITALIC);
910 font.SetPointSize (font.GetPointSize() - 1);
911 _sound_output_details->SetFont (font);
913 RtAudio audio (DCPOMATIC_RTAUDIO_API);
914 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
916 auto dev = audio.getDeviceInfo (i);
917 if (dev.probed && dev.outputChannels > 0) {
918 _sound_output->Append (std_to_wx (dev.name));
920 } catch (RtAudioError&) {
921 /* Something went wrong so let's just ignore that device */
925 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
926 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
927 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
928 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
932 SoundPage::reset_to_default ()
934 Config::instance()->set_audio_mapping_to_default ();
938 SoundPage::map_changed (AudioMapping m)
940 Config::instance()->set_audio_mapping (m);
944 SoundPage::sound_changed ()
946 Config::instance()->set_sound (_sound->GetValue ());
950 SoundPage::sound_output_changed ()
952 RtAudio audio (DCPOMATIC_RTAUDIO_API);
953 auto const so = get_sound_output();
954 string default_device;
956 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
957 } catch (RtAudioError&) {
960 if (!so || *so == default_device) {
961 Config::instance()->unset_sound_output ();
963 Config::instance()->set_sound_output (*so);
968 SoundPage::config_changed ()
970 auto config = Config::instance ();
972 checked_set (_sound, config->sound ());
974 auto const current_so = get_sound_output ();
975 optional<string> configured_so;
977 if (config->sound_output()) {
978 configured_so = config->sound_output().get();
980 /* No configured output means we should use the default */
981 RtAudio audio (DCPOMATIC_RTAUDIO_API);
983 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
984 } catch (RtAudioError&) {
985 /* Probably no audio devices at all */
989 if (configured_so && current_so != configured_so) {
990 /* Update _sound_output with the configured value */
992 while (i < _sound_output->GetCount()) {
993 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
994 _sound_output->SetSelection (i);
1001 RtAudio audio (DCPOMATIC_RTAUDIO_API);
1003 map<int, wxString> apis;
1004 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
1005 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
1006 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
1007 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
1008 apis[RtAudio::UNIX_JACK] = _("JACK");
1009 apis[RtAudio::LINUX_ALSA] = _("ALSA");
1010 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1011 apis[RtAudio::LINUX_OSS] = _("OSS");
1012 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1015 if (configured_so) {
1016 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1018 auto info = audio.getDeviceInfo(i);
1019 if (info.name == *configured_so && info.outputChannels > 0) {
1020 channels = info.outputChannels;
1022 } catch (RtAudioError&) {
1028 _sound_output_details->SetLabel (
1029 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1032 _map->set (Config::instance()->audio_mapping(channels));
1034 vector<NamedChannel> input;
1035 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1036 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1038 _map->set_input_channels (input);
1040 vector<NamedChannel> output;
1041 for (int i = 0; i < channels; ++i) {
1042 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1044 _map->set_output_channels (output);
1046 setup_sensitivity ();
1050 SoundPage::setup_sensitivity ()
1052 _sound_output->Enable (_sound->GetValue());
1055 /** @return Currently-selected preview sound output in the dialogue */
1057 SoundPage::get_sound_output ()
1059 int const sel = _sound_output->GetSelection ();
1060 if (sel == wxNOT_FOUND) {
1061 return optional<string> ();
1064 return wx_to_std (_sound_output->GetString (sel));
1068 LocationsPage::LocationsPage (wxSize panel_size, int border)
1069 : Page (panel_size, border)
1075 LocationsPage::GetName () const
1077 return _("Locations");
1080 #ifdef DCPOMATIC_OSX
1082 LocationsPage::GetLargeIcon () const
1084 return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
1089 LocationsPage::setup ()
1093 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1094 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1096 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1097 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1098 table->Add (_content_directory, wxGBPosition (r, 1));
1101 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1102 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1103 table->Add (_playlist_directory, wxGBPosition (r, 1));
1106 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1107 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1108 table->Add (_kdm_directory, wxGBPosition (r, 1));
1111 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1112 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1113 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1117 LocationsPage::config_changed ()
1119 auto config = Config::instance ();
1121 if (config->player_content_directory()) {
1122 checked_set (_content_directory, *config->player_content_directory());
1124 if (config->player_playlist_directory()) {
1125 checked_set (_playlist_directory, *config->player_playlist_directory());
1127 if (config->player_kdm_directory()) {
1128 checked_set (_kdm_directory, *config->player_kdm_directory());
1133 LocationsPage::content_directory_changed ()
1135 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1139 LocationsPage::playlist_directory_changed ()
1141 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1145 LocationsPage::kdm_directory_changed ()
1147 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));