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> (
586 CERTIFICATE_VALIDITY_PERIOD,
588 d->organisational_unit (),
589 d->root_common_name (),
590 d->intermediate_common_name (),
591 d->leaf_common_name ()
595 update_certificate_list ();
596 update_private_key ();
603 CertificateChainEditor::update_sensitivity ()
605 /* We can only remove the leaf certificate */
606 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
607 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
611 CertificateChainEditor::update_private_key ()
613 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
618 CertificateChainEditor::import_private_key ()
620 auto d = new wxFileDialog (this, _("Select Key File"));
622 if (d->ShowModal() == wxID_OK) {
624 boost::filesystem::path p (wx_to_std (d->GetPath ()));
625 if (boost::filesystem::file_size (p) > 8192) {
628 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
633 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
634 chain->set_key (dcp::file_to_string (p));
636 update_private_key ();
637 } catch (std::exception& e) {
638 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
644 update_sensitivity ();
648 CertificateChainEditor::export_private_key ()
650 auto key = _get()->key();
655 auto d = new wxFileDialog (
656 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
657 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
660 if (d->ShowModal () == wxID_OK) {
661 boost::filesystem::path path (wx_to_std(d->GetPath()));
662 if (path.extension() != ".pem") {
665 auto f = fopen_boost (path, "w");
667 throw OpenFileError (path, errno, OpenFileError::WRITE);
670 auto const s = _get()->key().get ();
671 checked_fwrite (s.c_str(), s.length(), f, path);
678 KeysPage::GetName () const
686 wxFont subheading_font (*wxNORMAL_FONT);
687 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
689 auto sizer = _panel->GetSizer();
692 auto m = new StaticText (_panel, _("Decrypting KDMs"));
693 m->SetFont (subheading_font);
694 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
697 auto buttons = new wxBoxSizer (wxVERTICAL);
699 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
700 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
701 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
702 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
703 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
704 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
705 auto decryption_advanced = new Button (_panel, _("Advanced..."));
706 buttons->Add (decryption_advanced, 0);
708 sizer->Add (buttons, 0, wxLEFT, _border);
710 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
711 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
712 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
713 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
716 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
717 m->SetFont (subheading_font);
718 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
721 auto signing_advanced = new Button (_panel, _("Advanced..."));
722 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
723 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
727 KeysPage::decryption_advanced ()
729 auto c = new CertificateChainEditor (
730 _panel, _("Decrypting KDMs"), _border,
731 bind(&Config::set_decryption_chain, Config::instance(), _1),
732 bind(&Config::decryption_chain, Config::instance()),
733 bind(&KeysPage::nag_alter_decryption_chain, this)
740 KeysPage::signing_advanced ()
742 auto c = new CertificateChainEditor (
743 _panel, _("Signing DCPs and KDMs"), _border,
744 bind(&Config::set_signer_chain, Config::instance(), _1),
745 bind(&Config::signer_chain, Config::instance()),
753 KeysPage::export_decryption_chain_and_key ()
755 auto d = new wxFileDialog (
756 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
757 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
760 if (d->ShowModal () == wxID_OK) {
761 boost::filesystem::path path (wx_to_std(d->GetPath()));
762 auto f = fopen_boost (path, "w");
764 throw OpenFileError (path, errno, OpenFileError::WRITE);
767 auto const chain = Config::instance()->decryption_chain()->chain();
768 checked_fwrite (chain.c_str(), chain.length(), f, path);
769 optional<string> const key = Config::instance()->decryption_chain()->key();
770 DCPOMATIC_ASSERT (key);
771 checked_fwrite (key->c_str(), key->length(), f, path);
779 KeysPage::import_decryption_chain_and_key ()
781 if (NagDialog::maybe_nag (
783 Config::NAG_IMPORT_DECRYPTION_CHAIN,
784 _("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!"),
790 auto d = new wxFileDialog (
791 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
794 if (d->ShowModal () == wxID_OK) {
795 auto new_chain = make_shared<dcp::CertificateChain>();
797 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
799 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
805 if (fgets (buffer, 128, f) == 0) {
809 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
810 new_chain->add (dcp::Certificate (current));
812 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
813 new_chain->set_key (current);
819 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
820 Config::instance()->set_decryption_chain (new_chain);
822 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
829 KeysPage::nag_alter_decryption_chain ()
831 return NagDialog::maybe_nag (
833 Config::NAG_ALTER_DECRYPTION_CHAIN,
834 _("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!"),
840 KeysPage::export_decryption_certificate ()
842 auto config = Config::instance();
843 wxString default_name = "dcpomatic";
844 if (!config->dcp_creator().empty()) {
845 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
847 if (!config->dcp_issuer().empty()) {
848 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
850 default_name += wxT("_kdm_decryption_cert.pem");
852 auto d = new wxFileDialog (
853 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
854 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
857 if (d->ShowModal () == wxID_OK) {
858 boost::filesystem::path path (wx_to_std(d->GetPath()));
859 if (path.extension() != ".pem") {
862 auto f = fopen_boost (path, "w");
864 throw OpenFileError (path, errno, OpenFileError::WRITE);
867 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
868 checked_fwrite (s.c_str(), s.length(), f, path);
876 SoundPage::GetName () const
884 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
885 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
889 _sound = new CheckBox (_panel, _("Play sound via"));
890 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
891 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
892 _sound_output = new wxChoice (_panel, wxID_ANY);
893 s->Add (_sound_output, 0);
894 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
895 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
896 table->Add (s, wxGBPosition(r, 1));
899 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
900 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
901 _map->SetSize (-1, 400);
902 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
905 _reset_to_default = new Button (_panel, _("Reset to default"));
906 table->Add (_reset_to_default, wxGBPosition(r, 1));
909 wxFont font = _sound_output_details->GetFont();
910 font.SetStyle (wxFONTSTYLE_ITALIC);
911 font.SetPointSize (font.GetPointSize() - 1);
912 _sound_output_details->SetFont (font);
914 RtAudio audio (DCPOMATIC_RTAUDIO_API);
915 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
917 auto dev = audio.getDeviceInfo (i);
918 if (dev.probed && dev.outputChannels > 0) {
919 _sound_output->Append (std_to_wx (dev.name));
921 } catch (RtAudioError&) {
922 /* Something went wrong so let's just ignore that device */
926 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
927 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
928 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
929 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
933 SoundPage::reset_to_default ()
935 Config::instance()->set_audio_mapping_to_default ();
939 SoundPage::map_changed (AudioMapping m)
941 Config::instance()->set_audio_mapping (m);
945 SoundPage::sound_changed ()
947 Config::instance()->set_sound (_sound->GetValue ());
951 SoundPage::sound_output_changed ()
953 RtAudio audio (DCPOMATIC_RTAUDIO_API);
954 auto const so = get_sound_output();
955 string default_device;
957 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
958 } catch (RtAudioError&) {
961 if (!so || *so == default_device) {
962 Config::instance()->unset_sound_output ();
964 Config::instance()->set_sound_output (*so);
969 SoundPage::config_changed ()
971 auto config = Config::instance ();
973 checked_set (_sound, config->sound ());
975 auto const current_so = get_sound_output ();
976 optional<string> configured_so;
978 if (config->sound_output()) {
979 configured_so = config->sound_output().get();
981 /* No configured output means we should use the default */
982 RtAudio audio (DCPOMATIC_RTAUDIO_API);
984 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
985 } catch (RtAudioError&) {
986 /* Probably no audio devices at all */
990 if (configured_so && current_so != configured_so) {
991 /* Update _sound_output with the configured value */
993 while (i < _sound_output->GetCount()) {
994 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
995 _sound_output->SetSelection (i);
1002 RtAudio audio (DCPOMATIC_RTAUDIO_API);
1004 map<int, wxString> apis;
1005 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
1006 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
1007 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
1008 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
1009 apis[RtAudio::UNIX_JACK] = _("JACK");
1010 apis[RtAudio::LINUX_ALSA] = _("ALSA");
1011 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1012 apis[RtAudio::LINUX_OSS] = _("OSS");
1013 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1016 if (configured_so) {
1017 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1019 auto info = audio.getDeviceInfo(i);
1020 if (info.name == *configured_so && info.outputChannels > 0) {
1021 channels = info.outputChannels;
1023 } catch (RtAudioError&) {
1029 _sound_output_details->SetLabel (
1030 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1033 _map->set (Config::instance()->audio_mapping(channels));
1035 vector<NamedChannel> input;
1036 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1037 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1039 _map->set_input_channels (input);
1041 vector<NamedChannel> output;
1042 for (int i = 0; i < channels; ++i) {
1043 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1045 _map->set_output_channels (output);
1047 setup_sensitivity ();
1051 SoundPage::setup_sensitivity ()
1053 _sound_output->Enable (_sound->GetValue());
1056 /** @return Currently-selected preview sound output in the dialogue */
1058 SoundPage::get_sound_output ()
1060 int const sel = _sound_output->GetSelection ();
1061 if (sel == wxNOT_FOUND) {
1062 return optional<string> ();
1065 return wx_to_std (_sound_output->GetString (sel));
1069 LocationsPage::LocationsPage (wxSize panel_size, int border)
1070 : Page (panel_size, border)
1076 LocationsPage::GetName () const
1078 return _("Locations");
1081 #ifdef DCPOMATIC_OSX
1083 LocationsPage::GetLargeIcon () const
1085 return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
1090 LocationsPage::setup ()
1094 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1095 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1097 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1098 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1099 table->Add (_content_directory, wxGBPosition (r, 1));
1102 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1103 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1104 table->Add (_playlist_directory, wxGBPosition (r, 1));
1107 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1108 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1109 table->Add (_kdm_directory, wxGBPosition (r, 1));
1112 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1113 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1114 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1118 LocationsPage::config_changed ()
1120 auto config = Config::instance ();
1122 if (config->player_content_directory()) {
1123 checked_set (_content_directory, *config->player_content_directory());
1125 if (config->player_playlist_directory()) {
1126 checked_set (_playlist_directory, *config->player_playlist_directory());
1128 if (config->player_kdm_directory()) {
1129 checked_set (_kdm_directory, *config->player_kdm_directory());
1134 LocationsPage::content_directory_changed ()
1136 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1140 LocationsPage::playlist_directory_changed ()
1142 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1146 LocationsPage::kdm_directory_changed ()
1148 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));