2 Copyright (C) 2012-2019 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>
36 using boost::optional;
37 using boost::shared_ptr;
38 using boost::function;
47 Page::Page (wxSize panel_size, int border)
50 , _panel_size (panel_size)
51 , _window_exists (false)
53 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
57 Page::create_window (wxWindow* parent)
59 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
60 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
64 _window_exists = true;
67 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
73 Page::config_changed_wrapper ()
81 Page::window_destroyed ()
83 _window_exists = false;
87 StockPage::StockPage (Kind kind, wxSize panel_size, int border)
88 : wxStockPreferencesPage (kind)
89 , Page (panel_size, border)
95 StockPage::CreateWindow (wxWindow* parent)
97 return create_window (parent);
100 StandardPage::StandardPage (wxSize panel_size, int border)
101 : Page (panel_size, border)
107 StandardPage::CreateWindow (wxWindow* parent)
109 return create_window (parent);
112 GeneralPage::GeneralPage (wxSize panel_size, int border)
113 : StockPage (Kind_General, panel_size, border)
119 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
121 _set_language = new CheckBox (_panel, _("Set language"));
122 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
123 _language = new wxChoice (_panel, wxID_ANY);
124 vector<pair<string, string> > languages;
125 languages.push_back (make_pair ("Čeština", "cs_CZ"));
126 languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
127 languages.push_back (make_pair ("Dansk", "da_DK"));
128 languages.push_back (make_pair ("Deutsch", "de_DE"));
129 languages.push_back (make_pair ("English", "en_GB"));
130 languages.push_back (make_pair ("Español", "es_ES"));
131 languages.push_back (make_pair ("Français", "fr_FR"));
132 languages.push_back (make_pair ("Italiano", "it_IT"));
133 languages.push_back (make_pair ("Nederlands", "nl_NL"));
134 languages.push_back (make_pair ("Русский", "ru_RU"));
135 languages.push_back (make_pair ("Polski", "pl_PL"));
136 languages.push_back (make_pair ("Português europeu", "pt_PT"));
137 languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
138 languages.push_back (make_pair ("Svenska", "sv_SE"));
139 languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
140 languages.push_back (make_pair ("Türkçe", "tr_TR"));
141 languages.push_back (make_pair ("українська мова", "uk_UA"));
142 checked_set (_language, languages);
143 table->Add (_language, wxGBPosition (r, 1));
146 wxStaticText* restart = add_label_to_sizer (
147 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
149 wxFont font = restart->GetFont();
150 font.SetStyle (wxFONTSTYLE_ITALIC);
151 font.SetPointSize (font.GetPointSize() - 1);
152 restart->SetFont (font);
155 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
156 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
160 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
162 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
163 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
166 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
167 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
170 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
171 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
175 GeneralPage::config_changed ()
177 Config* config = Config::instance ();
179 checked_set (_set_language, static_cast<bool>(config->language()));
181 /* Backwards compatibility of config file */
183 map<string, string> compat_map;
184 compat_map["fr"] = "fr_FR";
185 compat_map["it"] = "it_IT";
186 compat_map["es"] = "es_ES";
187 compat_map["sv"] = "sv_SE";
188 compat_map["de"] = "de_DE";
189 compat_map["nl"] = "nl_NL";
190 compat_map["ru"] = "ru_RU";
191 compat_map["pl"] = "pl_PL";
192 compat_map["da"] = "da_DK";
193 compat_map["pt"] = "pt_PT";
194 compat_map["sk"] = "sk_SK";
195 compat_map["cs"] = "cs_CZ";
196 compat_map["uk"] = "uk_UA";
198 string lang = config->language().get_value_or ("en_GB");
199 if (compat_map.find (lang) != compat_map.end ()) {
200 lang = compat_map[lang];
203 checked_set (_language, lang);
205 checked_set (_check_for_updates, config->check_for_updates ());
206 checked_set (_check_for_test_updates, config->check_for_test_updates ());
208 setup_sensitivity ();
212 GeneralPage::setup_sensitivity ()
214 _language->Enable (_set_language->GetValue ());
215 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
219 GeneralPage::set_language_changed ()
221 setup_sensitivity ();
222 if (_set_language->GetValue ()) {
225 Config::instance()->unset_language ();
230 GeneralPage::language_changed ()
232 int const sel = _language->GetSelection ();
234 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
236 Config::instance()->unset_language ();
241 GeneralPage::check_for_updates_changed ()
243 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
247 GeneralPage::check_for_test_updates_changed ()
249 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
252 CertificateChainEditor::CertificateChainEditor (
256 function<void (shared_ptr<dcp::CertificateChain>)> set,
257 function<shared_ptr<const dcp::CertificateChain> (void)> get,
258 function<bool (void)> nag_alter
260 : wxDialog (parent, wxID_ANY, title)
263 , _nag_alter (nag_alter)
265 _sizer = new wxBoxSizer (wxVERTICAL);
267 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
268 _sizer->Add (certificates_sizer, 0, wxALL, border);
270 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
275 ip.SetText (_("Type"));
277 _certificates->InsertColumn (0, ip);
283 ip.SetText (_("Thumbprint"));
286 wxFont font = ip.GetFont ();
287 font.SetFamily (wxFONTFAMILY_TELETYPE);
290 _certificates->InsertColumn (1, ip);
293 certificates_sizer->Add (_certificates, 1, wxEXPAND);
296 wxSizer* s = new wxBoxSizer (wxVERTICAL);
297 _add_certificate = new Button (this, _("Add..."));
298 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
299 _remove_certificate = new Button (this, _("Remove"));
300 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
301 _export_certificate = new Button (this, _("Export certificate..."));
302 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
303 _export_chain = new Button (this, _("Export chain..."));
304 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
305 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
308 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
309 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
312 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
313 _private_key = new StaticText (this, wxT(""));
314 wxFont font = _private_key->GetFont ();
315 font.SetFamily (wxFONTFAMILY_TELETYPE);
316 _private_key->SetFont (font);
317 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
318 _import_private_key = new Button (this, _("Import..."));
319 table->Add (_import_private_key, wxGBPosition (r, 2));
320 _export_private_key = new Button (this, _("Export..."));
321 table->Add (_export_private_key, wxGBPosition (r, 3));
324 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
325 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
326 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
327 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
330 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
331 font = *wxSMALL_FONT;
332 font.SetWeight (wxFONTWEIGHT_BOLD);
333 _private_key_bad->SetFont (font);
334 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
337 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
338 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
339 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
340 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
341 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
342 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
343 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
344 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
345 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
347 wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
349 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
352 SetSizerAndFit (_sizer);
354 update_certificate_list ();
355 update_private_key ();
356 update_sensitivity ();
360 CertificateChainEditor::add_button (wxWindow* button)
362 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
367 CertificateChainEditor::add_certificate ()
369 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
371 if (d->ShowModal() == wxID_OK) {
376 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
377 } catch (boost::filesystem::filesystem_error& e) {
378 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
383 if (!extra.empty ()) {
386 _("This file contains other certificates (or other data) after its first certificate. "
387 "Only the first certificate will be used.")
390 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
392 if (!chain->chain_valid ()) {
395 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
396 "Add certificates in order from root to intermediate to leaf.")
401 update_certificate_list ();
403 } catch (dcp::MiscError& e) {
404 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
410 update_sensitivity ();
414 CertificateChainEditor::remove_certificate ()
417 /* Cancel was clicked */
421 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
426 _certificates->DeleteItem (i);
427 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
431 update_sensitivity ();
432 update_certificate_list ();
436 CertificateChainEditor::export_certificate ()
438 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
443 wxFileDialog* d = new wxFileDialog (
444 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
445 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
448 dcp::CertificateChain::List all = _get()->root_to_leaf ();
449 dcp::CertificateChain::List::iterator j = all.begin ();
450 for (int k = 0; k < i; ++k) {
454 if (d->ShowModal () == wxID_OK) {
455 boost::filesystem::path path (wx_to_std(d->GetPath()));
456 FILE* f = fopen_boost (path, "w");
458 throw OpenFileError (path, errno, OpenFileError::WRITE);
461 string const s = j->certificate (true);
462 checked_fwrite (s.c_str(), s.length(), f, path);
469 CertificateChainEditor::export_chain ()
471 wxFileDialog* d = new wxFileDialog (
472 this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
473 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
476 if (d->ShowModal () == wxID_OK) {
477 boost::filesystem::path path (wx_to_std(d->GetPath()));
478 FILE* f = fopen_boost (path, "w");
480 throw OpenFileError (path, errno, OpenFileError::WRITE);
483 string const s = _get()->chain();
484 checked_fwrite (s.c_str(), s.length(), f, path);
492 CertificateChainEditor::update_certificate_list ()
494 _certificates->DeleteAllItems ();
496 dcp::CertificateChain::List certs = _get()->root_to_leaf ();
497 BOOST_FOREACH (dcp::Certificate const & i, certs) {
500 _certificates->InsertItem (item);
501 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
504 _certificates->SetItem (n, 0, _("Root"));
505 } else if (n == (certs.size() - 1)) {
506 _certificates->SetItem (n, 0, _("Leaf"));
508 _certificates->SetItem (n, 0, _("Intermediate"));
514 static wxColour normal = _private_key_bad->GetForegroundColour ();
516 if (_get()->private_key_valid()) {
517 _private_key_bad->Hide ();
518 _private_key_bad->SetForegroundColour (normal);
520 _private_key_bad->Show ();
521 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
526 CertificateChainEditor::remake_certificates ()
528 shared_ptr<const dcp::CertificateChain> chain = _get();
530 string subject_organization_name;
531 string subject_organizational_unit_name;
532 string root_common_name;
533 string intermediate_common_name;
534 string leaf_common_name;
536 dcp::CertificateChain::List all = chain->root_to_leaf ();
538 if (all.size() >= 1) {
540 subject_organization_name = chain->root().subject_organization_name ();
541 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
542 root_common_name = chain->root().subject_common_name ();
545 if (all.size() >= 2) {
547 leaf_common_name = chain->leaf().subject_common_name ();
550 if (all.size() >= 3) {
551 /* Have an intermediate */
552 dcp::CertificateChain::List::iterator i = all.begin ();
554 intermediate_common_name = i->subject_common_name ();
558 /* Cancel was clicked */
562 MakeChainDialog* d = new MakeChainDialog (
564 subject_organization_name,
565 subject_organizational_unit_name,
567 intermediate_common_name,
571 if (d->ShowModal () == wxID_OK) {
573 shared_ptr<dcp::CertificateChain> (
574 new dcp::CertificateChain (
577 d->organisational_unit (),
578 d->root_common_name (),
579 d->intermediate_common_name (),
580 d->leaf_common_name ()
585 update_certificate_list ();
586 update_private_key ();
593 CertificateChainEditor::update_sensitivity ()
595 /* We can only remove the leaf certificate */
596 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
597 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
601 CertificateChainEditor::update_private_key ()
603 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
608 CertificateChainEditor::import_private_key ()
610 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
612 if (d->ShowModal() == wxID_OK) {
614 boost::filesystem::path p (wx_to_std (d->GetPath ()));
615 if (boost::filesystem::file_size (p) > 8192) {
618 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
623 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
624 chain->set_key (dcp::file_to_string (p));
626 update_private_key ();
627 } catch (dcp::MiscError& e) {
628 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
634 update_sensitivity ();
638 CertificateChainEditor::export_private_key ()
640 optional<string> key = _get()->key();
645 wxFileDialog* d = new wxFileDialog (
646 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
647 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
650 if (d->ShowModal () == wxID_OK) {
651 boost::filesystem::path path (wx_to_std(d->GetPath()));
652 FILE* f = fopen_boost (path, "w");
654 throw OpenFileError (path, errno, OpenFileError::WRITE);
657 string const s = _get()->key().get ();
658 checked_fwrite (s.c_str(), s.length(), f, path);
665 KeysPage::GetName () const
673 wxFont subheading_font (*wxNORMAL_FONT);
674 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
676 wxSizer* sizer = _panel->GetSizer();
679 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
680 m->SetFont (subheading_font);
681 sizer->Add (m, 0, wxALL, _border);
684 wxSizer* buttons = new wxBoxSizer (wxVERTICAL);
686 wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
687 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
688 wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
689 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
690 wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
691 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
692 wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
693 buttons->Add (decryption_advanced, 0);
695 sizer->Add (buttons, 0, wxLEFT, _border);
697 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
698 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
699 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
700 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
703 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
704 m->SetFont (subheading_font);
705 sizer->Add (m, 0, wxALL, _border);
708 wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
709 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
710 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
714 KeysPage::decryption_advanced ()
716 CertificateChainEditor* c = new CertificateChainEditor (
717 _panel, _("Decrypting KDMs"), _border,
718 bind (&Config::set_decryption_chain, Config::instance (), _1),
719 bind (&Config::decryption_chain, Config::instance ()),
720 bind (&KeysPage::nag_alter_decryption_chain, this)
727 KeysPage::signing_advanced ()
729 CertificateChainEditor* c = new CertificateChainEditor (
730 _panel, _("Signing DCPs and KDMs"), _border,
731 bind (&Config::set_signer_chain, Config::instance (), _1),
732 bind (&Config::signer_chain, Config::instance ()),
740 KeysPage::export_decryption_chain_and_key ()
742 wxFileDialog* d = new wxFileDialog (
743 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
744 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
747 if (d->ShowModal () == wxID_OK) {
748 boost::filesystem::path path (wx_to_std(d->GetPath()));
749 FILE* f = fopen_boost (path, "w");
751 throw OpenFileError (path, errno, OpenFileError::WRITE);
754 string const chain = Config::instance()->decryption_chain()->chain();
755 checked_fwrite (chain.c_str(), chain.length(), f, path);
756 optional<string> const key = Config::instance()->decryption_chain()->key();
757 DCPOMATIC_ASSERT (key);
758 checked_fwrite (key->c_str(), key->length(), f, path);
766 KeysPage::import_decryption_chain_and_key ()
768 if (NagDialog::maybe_nag (
770 Config::NAG_IMPORT_DECRYPTION_CHAIN,
771 _("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!"),
777 wxFileDialog* d = new wxFileDialog (
778 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
781 if (d->ShowModal () == wxID_OK) {
782 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
784 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
786 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
792 if (fgets (buffer, 128, f) == 0) {
796 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
797 new_chain->add (dcp::Certificate (current));
799 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
800 new_chain->set_key (current);
806 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
807 Config::instance()->set_decryption_chain (new_chain);
809 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
816 KeysPage::nag_alter_decryption_chain ()
818 return NagDialog::maybe_nag (
820 Config::NAG_ALTER_DECRYPTION_CHAIN,
821 _("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!"),
827 KeysPage::export_decryption_certificate ()
829 wxFileDialog* d = new wxFileDialog (
830 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
831 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
834 if (d->ShowModal () == wxID_OK) {
835 boost::filesystem::path path (wx_to_std(d->GetPath()));
836 FILE* f = fopen_boost (path, "w");
838 throw OpenFileError (path, errno, OpenFileError::WRITE);
841 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
842 checked_fwrite (s.c_str(), s.length(), f, path);
850 SoundPage::GetName () const
858 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
859 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
863 _sound = new CheckBox (_panel, _("Play sound via"));
864 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
865 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
866 _sound_output = new wxChoice (_panel, wxID_ANY);
867 s->Add (_sound_output, 0);
868 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
869 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
870 table->Add (s, wxGBPosition(r, 1));
873 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
874 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
875 _map->SetSize (-1, 600);
876 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
879 _reset_to_default = new Button (_panel, _("Reset to default"));
880 table->Add (_reset_to_default, wxGBPosition(r, 1));
883 wxFont font = _sound_output_details->GetFont();
884 font.SetStyle (wxFONTSTYLE_ITALIC);
885 font.SetPointSize (font.GetPointSize() - 1);
886 _sound_output_details->SetFont (font);
888 RtAudio audio (DCPOMATIC_RTAUDIO_API);
889 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
890 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
891 if (dev.probed && dev.outputChannels > 0) {
892 _sound_output->Append (std_to_wx (dev.name));
896 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
897 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
898 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
899 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
903 SoundPage::reset_to_default ()
905 Config::instance()->set_audio_mapping_to_default ();
909 SoundPage::map_changed (AudioMapping m)
911 Config::instance()->set_audio_mapping (m);
915 SoundPage::sound_changed ()
917 Config::instance()->set_sound (_sound->GetValue ());
921 SoundPage::sound_output_changed ()
923 RtAudio audio (DCPOMATIC_RTAUDIO_API);
924 optional<string> const so = get_sound_output();
925 if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
926 Config::instance()->unset_sound_output ();
928 Config::instance()->set_sound_output (*so);
933 SoundPage::config_changed ()
935 Config* config = Config::instance ();
937 checked_set (_sound, config->sound ());
939 optional<string> const current_so = get_sound_output ();
940 optional<string> configured_so;
942 if (config->sound_output()) {
943 configured_so = config->sound_output().get();
945 /* No configured output means we should use the default */
946 RtAudio audio (DCPOMATIC_RTAUDIO_API);
948 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
949 } catch (RtAudioError& e) {
950 /* Probably no audio devices at all */
954 if (configured_so && current_so != configured_so) {
955 /* Update _sound_output with the configured value */
957 while (i < _sound_output->GetCount()) {
958 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
959 _sound_output->SetSelection (i);
966 RtAudio audio (DCPOMATIC_RTAUDIO_API);
968 map<int, wxString> apis;
969 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
970 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
971 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
972 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
973 apis[RtAudio::UNIX_JACK] = _("JACK");
974 apis[RtAudio::LINUX_ALSA] = _("ALSA");
975 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
976 apis[RtAudio::LINUX_OSS] = _("OSS");
977 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
981 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
982 RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
983 if (info.name == *configured_so && info.outputChannels > 0) {
984 channels = info.outputChannels;
989 _sound_output_details->SetLabel (
990 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
993 _map->set (Config::instance()->audio_mapping(channels));
995 vector<string> input;
996 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
997 input.push_back (short_audio_channel_name(i));
999 _map->set_input_channels (input);
1001 vector<string> output;
1002 for (int i = 0; i < channels; ++i) {
1003 output.push_back (dcp::raw_convert<string>(i));
1005 _map->set_output_channels (output);
1007 setup_sensitivity ();
1011 SoundPage::setup_sensitivity ()
1013 _sound_output->Enable (_sound->GetValue());
1016 /** @return Currently-selected preview sound output in the dialogue */
1018 SoundPage::get_sound_output ()
1020 int const sel = _sound_output->GetSelection ();
1021 if (sel == wxNOT_FOUND) {
1022 return optional<string> ();
1025 return wx_to_std (_sound_output->GetString (sel));
1029 LocationsPage::LocationsPage (wxSize panel_size, int border)
1030 : StandardPage (panel_size, border)
1036 LocationsPage::GetName () const
1038 return _("Locations");
1041 #ifdef DCPOMATIC_OSX
1043 LocationsPage::GetLargeIcon () const
1045 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1050 LocationsPage::setup ()
1055 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1056 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1058 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1059 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1060 table->Add (_content_directory, wxGBPosition (r, 1));
1063 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1064 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1065 table->Add (_playlist_directory, wxGBPosition (r, 1));
1068 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1069 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1070 table->Add (_kdm_directory, wxGBPosition (r, 1));
1073 #ifdef DCPOMATIC_VARIANT_SWAROOP
1074 add_label_to_sizer (table, _panel, _("Background image"), true, wxGBPosition (r, 0));
1075 _background_image = new FilePickerCtrl (_panel, _("Select image file"), "*.png;*.jpg;*.jpeg;*.tif;*.tiff", true, false);
1076 table->Add (_background_image, wxGBPosition (r, 1));
1080 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1081 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1082 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1083 #ifdef DCPOMATIC_VARIANT_SWAROOP
1084 _background_image->Bind (wxEVT_FILEPICKER_CHANGED, bind(&LocationsPage::background_image_changed, this));
1089 LocationsPage::config_changed ()
1091 Config* config = Config::instance ();
1093 if (config->player_content_directory()) {
1094 checked_set (_content_directory, *config->player_content_directory());
1096 if (config->player_playlist_directory()) {
1097 checked_set (_playlist_directory, *config->player_playlist_directory());
1099 if (config->player_kdm_directory()) {
1100 checked_set (_kdm_directory, *config->player_kdm_directory());
1102 #ifdef DCPOMATIC_VARIANT_SWAROOP
1103 if (config->player_background_image()) {
1104 checked_set (_background_image, *config->player_background_image());
1110 LocationsPage::content_directory_changed ()
1112 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1116 LocationsPage::playlist_directory_changed ()
1118 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1122 LocationsPage::kdm_directory_changed ()
1124 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));
1127 #ifdef DCPOMATIC_VARIANT_SWAROOP
1129 LocationsPage::background_image_changed ()
1131 boost::filesystem::path const f = wx_to_std(_background_image->GetPath());
1132 if (!boost::filesystem::is_regular_file(f) || !wxImage::CanRead(std_to_wx(f.string()))) {
1133 error_dialog (0, _("Could not load image file."));
1134 if (Config::instance()->player_background_image()) {
1135 checked_set (_background_image, *Config::instance()->player_background_image());
1140 Config::instance()->set_player_background_image(f);