2 Copyright (C) 2012-2017 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 "nag_dialog.h"
33 Page::Page (wxSize panel_size, int border)
36 , _panel_size (panel_size)
37 , _window_exists (false)
39 _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
43 Page::create_window (wxWindow* parent)
45 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
46 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
50 _window_exists = true;
53 _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
59 Page::config_changed_wrapper ()
67 Page::window_destroyed ()
69 _window_exists = false;
73 StockPage::StockPage (Kind kind, wxSize panel_size, int border)
74 : wxStockPreferencesPage (kind)
75 , Page (panel_size, border)
81 StockPage::CreateWindow (wxWindow* parent)
83 return create_window (parent);
86 StandardPage::StandardPage (wxSize panel_size, int border)
87 : Page (panel_size, border)
93 StandardPage::CreateWindow (wxWindow* parent)
95 return create_window (parent);
98 GeneralPage::GeneralPage (wxSize panel_size, int border)
99 : StockPage (Kind_General, panel_size, border)
105 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
107 _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
108 table->Add (_set_language, wxGBPosition (r, 0));
109 _language = new wxChoice (_panel, wxID_ANY);
110 std::vector<std::pair<std::string, std::string> > languages;
111 languages.push_back (std::make_pair ("Čeština", "cs_CZ"));
112 languages.push_back (std::make_pair ("汉语/漢語", "zh_CN"));
113 languages.push_back (std::make_pair ("Dansk", "da_DK"));
114 languages.push_back (std::make_pair ("Deutsch", "de_DE"));
115 languages.push_back (std::make_pair ("English", "en_GB"));
116 languages.push_back (std::make_pair ("Español", "es_ES"));
117 languages.push_back (std::make_pair ("Français", "fr_FR"));
118 languages.push_back (std::make_pair ("Italiano", "it_IT"));
119 languages.push_back (std::make_pair ("Nederlands", "nl_NL"));
120 languages.push_back (std::make_pair ("Русский", "ru_RU"));
121 languages.push_back (std::make_pair ("Polski", "pl_PL"));
122 languages.push_back (std::make_pair ("Português europeu", "pt_PT"));
123 languages.push_back (std::make_pair ("Português do Brasil", "pt_BR"));
124 languages.push_back (std::make_pair ("Svenska", "sv_SE"));
125 languages.push_back (std::make_pair ("Slovenský jazyk", "sk_SK"));
126 languages.push_back (std::make_pair ("українська мова", "uk_UA"));
127 checked_set (_language, languages);
128 table->Add (_language, wxGBPosition (r, 1));
131 wxStaticText* restart = add_label_to_sizer (
132 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
134 wxFont font = restart->GetFont();
135 font.SetStyle (wxFONTSTYLE_ITALIC);
136 font.SetPointSize (font.GetPointSize() - 1);
137 restart->SetFont (font);
140 _set_language->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::set_language_changed, this));
141 _language->Bind (wxEVT_CHOICE, boost::bind (&GeneralPage::language_changed, this));
145 GeneralPage::add_play_sound_controls (wxGridBagSizer* table, int& r)
147 _sound = new wxCheckBox (_panel, wxID_ANY, _("Play sound via"));
148 table->Add (_sound, wxGBPosition (r, 0));
149 _sound_output = new wxChoice (_panel, wxID_ANY);
150 table->Add (_sound_output, wxGBPosition (r, 1));
153 RtAudio audio (DCPOMATIC_RTAUDIO_API);
154 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
155 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
156 if (dev.probed && dev.outputChannels > 0) {
157 _sound_output->Append (std_to_wx (dev.name));
161 _sound->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::sound_changed, this));
162 _sound_output->Bind (wxEVT_CHOICE, boost::bind (&GeneralPage::sound_output_changed, this));
166 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
168 _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for updates on startup"));
169 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
172 _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates on startup"));
173 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
176 _check_for_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_updates_changed, this));
177 _check_for_test_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
181 GeneralPage::config_changed ()
183 Config* config = Config::instance ();
185 checked_set (_set_language, static_cast<bool>(config->language()));
187 /* Backwards compatibility of config file */
189 std::map<std::string, std::string> compat_map;
190 compat_map["fr"] = "fr_FR";
191 compat_map["it"] = "it_IT";
192 compat_map["es"] = "es_ES";
193 compat_map["sv"] = "sv_SE";
194 compat_map["de"] = "de_DE";
195 compat_map["nl"] = "nl_NL";
196 compat_map["ru"] = "ru_RU";
197 compat_map["pl"] = "pl_PL";
198 compat_map["da"] = "da_DK";
199 compat_map["pt"] = "pt_PT";
200 compat_map["sk"] = "sk_SK";
201 compat_map["cs"] = "cs_CZ";
202 compat_map["uk"] = "uk_UA";
204 std::string lang = config->language().get_value_or ("en_GB");
205 if (compat_map.find (lang) != compat_map.end ()) {
206 lang = compat_map[lang];
209 checked_set (_language, lang);
211 checked_set (_check_for_updates, config->check_for_updates ());
212 checked_set (_check_for_test_updates, config->check_for_test_updates ());
214 checked_set (_sound, config->sound ());
216 boost::optional<std::string> const current_so = get_sound_output ();
217 boost::optional<std::string> configured_so;
219 if (config->sound_output()) {
220 configured_so = config->sound_output().get();
222 /* No configured output means we should use the default */
223 RtAudio audio (DCPOMATIC_RTAUDIO_API);
225 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
226 } catch (RtAudioError& e) {
227 /* Probably no audio devices at all */
231 if (configured_so && current_so != configured_so) {
232 /* Update _sound_output with the configured value */
234 while (i < _sound_output->GetCount()) {
235 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
236 _sound_output->SetSelection (i);
243 setup_sensitivity ();
247 GeneralPage::setup_sensitivity ()
249 _language->Enable (_set_language->GetValue ());
250 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
251 _sound_output->Enable (_sound->GetValue ());
254 /** @return Currently-selected preview sound output in the dialogue */
255 boost::optional<std::string>
256 GeneralPage::get_sound_output ()
258 int const sel = _sound_output->GetSelection ();
259 if (sel == wxNOT_FOUND) {
260 return boost::optional<std::string> ();
263 return wx_to_std (_sound_output->GetString (sel));
267 GeneralPage::set_language_changed ()
269 setup_sensitivity ();
270 if (_set_language->GetValue ()) {
273 Config::instance()->unset_language ();
278 GeneralPage::language_changed ()
280 int const sel = _language->GetSelection ();
282 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
284 Config::instance()->unset_language ();
289 GeneralPage::check_for_updates_changed ()
291 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
295 GeneralPage::check_for_test_updates_changed ()
297 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
301 GeneralPage::sound_changed ()
303 Config::instance()->set_sound (_sound->GetValue ());
307 GeneralPage::sound_output_changed ()
309 RtAudio audio (DCPOMATIC_RTAUDIO_API);
310 boost::optional<std::string> const so = get_sound_output();
311 if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
312 Config::instance()->unset_sound_output ();
314 Config::instance()->set_sound_output (*so);
318 CertificateChainEditor::CertificateChainEditor (
322 boost::function<void (boost::shared_ptr<dcp::CertificateChain>)> set,
323 boost::function<boost::shared_ptr<const dcp::CertificateChain> (void)> get,
324 boost::function<void (void)> nag_remake
329 , _nag_remake (nag_remake)
331 wxFont subheading_font (*wxNORMAL_FONT);
332 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
334 _sizer = new wxBoxSizer (wxVERTICAL);
337 wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
338 m->SetFont (subheading_font);
339 _sizer->Add (m, 0, wxALL, border);
342 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
343 _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
345 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
350 ip.SetText (_("Type"));
352 _certificates->InsertColumn (0, ip);
358 ip.SetText (_("Thumbprint"));
361 wxFont font = ip.GetFont ();
362 font.SetFamily (wxFONTFAMILY_TELETYPE);
365 _certificates->InsertColumn (1, ip);
368 certificates_sizer->Add (_certificates, 1, wxEXPAND);
371 wxSizer* s = new wxBoxSizer (wxVERTICAL);
372 _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
373 s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
374 _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
375 s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
376 _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
377 s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
378 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
381 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
382 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
385 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
386 _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
387 wxFont font = _private_key->GetFont ();
388 font.SetFamily (wxFONTFAMILY_TELETYPE);
389 _private_key->SetFont (font);
390 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
391 _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
392 table->Add (_load_private_key, wxGBPosition (r, 2));
393 _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
394 table->Add (_export_private_key, wxGBPosition (r, 3));
397 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
398 _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates\nand key..."));
399 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
400 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
403 _private_key_bad = new wxStaticText (this, wxID_ANY, _("Leaf private key does not match leaf certificate!"));
404 font = *wxSMALL_FONT;
405 font.SetWeight (wxFONTWEIGHT_BOLD);
406 _private_key_bad->SetFont (font);
407 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
410 _add_certificate->Bind (wxEVT_BUTTON, boost::bind (&CertificateChainEditor::add_certificate, this));
411 _remove_certificate->Bind (wxEVT_BUTTON, boost::bind (&CertificateChainEditor::remove_certificate, this));
412 _export_certificate->Bind (wxEVT_BUTTON, boost::bind (&CertificateChainEditor::export_certificate, this));
413 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
414 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
415 _remake_certificates->Bind (wxEVT_BUTTON, boost::bind (&CertificateChainEditor::remake_certificates, this));
416 _load_private_key->Bind (wxEVT_BUTTON, boost::bind (&CertificateChainEditor::load_private_key, this));
417 _export_private_key->Bind (wxEVT_BUTTON, boost::bind (&CertificateChainEditor::export_private_key, this));
419 SetSizerAndFit (_sizer);
424 CertificateChainEditor::config_changed ()
426 _chain.reset (new dcp::CertificateChain (*_get().get ()));
428 update_certificate_list ();
429 update_private_key ();
430 update_sensitivity ();
434 CertificateChainEditor::add_button (wxWindow* button)
436 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
441 CertificateChainEditor::add_certificate ()
443 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
445 if (d->ShowModal() == wxID_OK) {
450 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
451 } catch (boost::filesystem::filesystem_error& e) {
452 error_dialog (this, wxString::Format (_("Could not load certificate (%s)"), d->GetPath().data()));
457 if (!extra.empty ()) {
460 _("This file contains other certificates (or other data) after its first certificate. "
461 "Only the first certificate will be used.")
465 if (!_chain->chain_valid ()) {
468 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
469 "Add certificates in order from root to intermediate to leaf.")
474 update_certificate_list ();
476 } catch (dcp::MiscError& e) {
477 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
483 update_sensitivity ();
487 CertificateChainEditor::remove_certificate ()
489 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
494 _certificates->DeleteItem (i);
498 update_sensitivity ();
502 CertificateChainEditor::export_certificate ()
504 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
509 wxFileDialog* d = new wxFileDialog (
510 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
511 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
514 dcp::CertificateChain::List all = _chain->root_to_leaf ();
515 dcp::CertificateChain::List::iterator j = all.begin ();
516 for (int k = 0; k < i; ++k) {
520 if (d->ShowModal () == wxID_OK) {
521 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
523 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
526 std::string const s = j->certificate (true);
527 fwrite (s.c_str(), 1, s.length(), f);
534 CertificateChainEditor::update_certificate_list ()
536 _certificates->DeleteAllItems ();
538 dcp::CertificateChain::List certs = _chain->root_to_leaf ();
539 BOOST_FOREACH (dcp::Certificate const & i, certs) {
542 _certificates->InsertItem (item);
543 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
546 _certificates->SetItem (n, 0, _("Root"));
547 } else if (n == (certs.size() - 1)) {
548 _certificates->SetItem (n, 0, _("Leaf"));
550 _certificates->SetItem (n, 0, _("Intermediate"));
556 static wxColour normal = _private_key_bad->GetForegroundColour ();
558 if (_chain->private_key_valid ()) {
559 _private_key_bad->Hide ();
560 _private_key_bad->SetForegroundColour (normal);
562 _private_key_bad->Show ();
563 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
568 CertificateChainEditor::remake_certificates ()
570 boost::shared_ptr<const dcp::CertificateChain> chain = _get ();
572 std::string subject_organization_name;
573 std::string subject_organizational_unit_name;
574 std::string root_common_name;
575 std::string intermediate_common_name;
576 std::string leaf_common_name;
578 dcp::CertificateChain::List all = chain->root_to_leaf ();
580 if (all.size() >= 1) {
582 subject_organization_name = chain->root().subject_organization_name ();
583 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
584 root_common_name = chain->root().subject_common_name ();
587 if (all.size() >= 2) {
589 leaf_common_name = chain->leaf().subject_common_name ();
592 if (all.size() >= 3) {
593 /* Have an intermediate */
594 dcp::CertificateChain::List::iterator i = all.begin ();
596 intermediate_common_name = i->subject_common_name ();
601 MakeChainDialog* d = new MakeChainDialog (
603 subject_organization_name,
604 subject_organizational_unit_name,
606 intermediate_common_name,
610 if (d->ShowModal () == wxID_OK) {
612 new dcp::CertificateChain (
615 d->organisational_unit (),
616 d->root_common_name (),
617 d->intermediate_common_name (),
618 d->leaf_common_name ()
623 update_certificate_list ();
624 update_private_key ();
631 CertificateChainEditor::update_sensitivity ()
633 /* We can only remove the leaf certificate */
634 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
635 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
639 CertificateChainEditor::update_private_key ()
641 checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
646 CertificateChainEditor::load_private_key ()
648 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
650 if (d->ShowModal() == wxID_OK) {
652 boost::filesystem::path p (wx_to_std (d->GetPath ()));
653 if (boost::filesystem::file_size (p) > 8192) {
656 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
661 _chain->set_key (dcp::file_to_string (p));
663 update_private_key ();
664 } catch (dcp::MiscError& e) {
665 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
671 update_sensitivity ();
675 CertificateChainEditor::export_private_key ()
677 boost::optional<std::string> key = _chain->key ();
682 wxFileDialog* d = new wxFileDialog (
683 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
684 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
687 if (d->ShowModal () == wxID_OK) {
688 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
690 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
693 std::string const s = _chain->key().get ();
694 fwrite (s.c_str(), 1, s.length(), f);
701 KeysPage::GetName () const
710 _signer = new CertificateChainEditor (
711 _panel, _("Signing DCPs and KDMs"), _border,
712 bind (&Config::set_signer_chain, Config::instance (), _1),
713 bind (&Config::signer_chain, Config::instance ()),
717 _panel->GetSizer()->Add (_signer);
720 _decryption = new CertificateChainEditor (
721 _panel, _("Decrypting KDMs"), _border,
722 bind (&Config::set_decryption_chain, Config::instance (), _1),
723 bind (&Config::decryption_chain, Config::instance ()),
724 bind (&KeysPage::nag_remake_decryption_chain, this)
727 _panel->GetSizer()->Add (_decryption);
729 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\ncertificate..."));
730 _decryption->add_button (_export_decryption_certificate);
731 _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\nchain..."));
732 _decryption->add_button (_export_decryption_chain);
734 _export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
735 _export_decryption_chain->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain, this));
739 KeysPage::config_changed ()
742 _signer->config_changed ();
744 _decryption->config_changed ();
748 KeysPage::nag_remake_decryption_chain ()
750 NagDialog::maybe_nag (
752 Config::NAG_REMAKE_DECRYPTION_CHAIN,
753 _("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!")
758 KeysPage::export_decryption_chain ()
760 wxFileDialog* d = new wxFileDialog (
761 _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
762 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
765 if (d->ShowModal () == wxID_OK) {
766 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
768 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
771 std::string const s = Config::instance()->decryption_chain()->chain();
772 fwrite (s.c_str(), 1, s.length(), f);
779 KeysPage::export_decryption_certificate ()
781 wxFileDialog* d = new wxFileDialog (
782 _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
783 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
786 if (d->ShowModal () == wxID_OK) {
787 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
789 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
792 std::string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
793 fwrite (s.c_str(), 1, s.length(), f);