Correct capitalisation of PulseAudio.
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
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 <iostream>
27
28 using std::string;
29 using std::vector;
30 using std::pair;
31 using std::make_pair;
32 using std::map;
33 using boost::bind;
34 using boost::optional;
35 using boost::shared_ptr;
36 using boost::function;
37
38 static
39 bool
40 do_nothing ()
41 {
42         return false;
43 }
44
45 Page::Page (wxSize panel_size, int border)
46         : _border (border)
47         , _panel (0)
48         , _panel_size (panel_size)
49         , _window_exists (false)
50 {
51         _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
52 }
53
54 wxWindow*
55 Page::create_window (wxWindow* parent)
56 {
57         _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
58         wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
59         _panel->SetSizer (s);
60
61         setup ();
62         _window_exists = true;
63         config_changed ();
64
65         _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
66
67         return _panel;
68 }
69
70 void
71 Page::config_changed_wrapper ()
72 {
73         if (_window_exists) {
74                 config_changed ();
75         }
76 }
77
78 void
79 Page::window_destroyed ()
80 {
81         _window_exists = false;
82 }
83
84
85 StockPage::StockPage (Kind kind, wxSize panel_size, int border)
86         : wxStockPreferencesPage (kind)
87         , Page (panel_size, border)
88 {
89
90 }
91
92 wxWindow*
93 StockPage::CreateWindow (wxWindow* parent)
94 {
95         return create_window (parent);
96 }
97
98 StandardPage::StandardPage (wxSize panel_size, int border)
99         : Page (panel_size, border)
100 {
101
102 }
103
104 wxWindow*
105 StandardPage::CreateWindow (wxWindow* parent)
106 {
107         return create_window (parent);
108 }
109
110 GeneralPage::GeneralPage (wxSize panel_size, int border)
111         : StockPage (Kind_General, panel_size, border)
112 {
113
114 }
115
116 void
117 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
118 {
119         _set_language = new CheckBox (_panel, _("Set language"));
120         table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
121         _language = new wxChoice (_panel, wxID_ANY);
122         vector<pair<string, string> > languages;
123         languages.push_back (make_pair ("Čeština", "cs_CZ"));
124         languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
125         languages.push_back (make_pair ("Dansk", "da_DK"));
126         languages.push_back (make_pair ("Deutsch", "de_DE"));
127         languages.push_back (make_pair ("English", "en_GB"));
128         languages.push_back (make_pair ("Español", "es_ES"));
129         languages.push_back (make_pair ("Français", "fr_FR"));
130         languages.push_back (make_pair ("Italiano", "it_IT"));
131         languages.push_back (make_pair ("Nederlands", "nl_NL"));
132         languages.push_back (make_pair ("Русский", "ru_RU"));
133         languages.push_back (make_pair ("Polski", "pl_PL"));
134         languages.push_back (make_pair ("Português europeu", "pt_PT"));
135         languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
136         languages.push_back (make_pair ("Svenska", "sv_SE"));
137         languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
138         languages.push_back (make_pair ("Türkçe", "tr_TR"));
139         languages.push_back (make_pair ("українська мова", "uk_UA"));
140         checked_set (_language, languages);
141         table->Add (_language, wxGBPosition (r, 1));
142         ++r;
143
144         wxStaticText* restart = add_label_to_sizer (
145                 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
146                 );
147         wxFont font = restart->GetFont();
148         font.SetStyle (wxFONTSTYLE_ITALIC);
149         font.SetPointSize (font.GetPointSize() - 1);
150         restart->SetFont (font);
151         ++r;
152
153         _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
154         _language->Bind     (wxEVT_CHOICE,   bind (&GeneralPage::language_changed,     this));
155 }
156
157 void
158 GeneralPage::add_play_sound_controls (wxGridBagSizer* table, int& r)
159 {
160         _sound = new CheckBox (_panel, _("Play sound via"));
161         table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
162         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
163         _sound_output = new wxChoice (_panel, wxID_ANY);
164         s->Add (_sound_output, 0);
165         _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
166         s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
167         table->Add (s, wxGBPosition(r, 1));
168         ++r;
169
170         wxFont font = _sound_output_details->GetFont();
171         font.SetStyle (wxFONTSTYLE_ITALIC);
172         font.SetPointSize (font.GetPointSize() - 1);
173         _sound_output_details->SetFont (font);
174
175         RtAudio audio (DCPOMATIC_RTAUDIO_API);
176         for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
177                 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
178                 if (dev.probed && dev.outputChannels > 0) {
179                         _sound_output->Append (std_to_wx (dev.name));
180                 }
181         }
182
183         _sound->Bind        (wxEVT_CHECKBOX, bind (&GeneralPage::sound_changed, this));
184         _sound_output->Bind (wxEVT_CHOICE,   bind (&GeneralPage::sound_output_changed, this));
185 }
186
187 void
188 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
189 {
190         _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
191         table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
192         ++r;
193
194         _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
195         table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
196         ++r;
197
198         _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
199         _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
200 }
201
202 void
203 GeneralPage::config_changed ()
204 {
205         Config* config = Config::instance ();
206
207         checked_set (_set_language, static_cast<bool>(config->language()));
208
209         /* Backwards compatibility of config file */
210
211         map<string, string> compat_map;
212         compat_map["fr"] = "fr_FR";
213         compat_map["it"] = "it_IT";
214         compat_map["es"] = "es_ES";
215         compat_map["sv"] = "sv_SE";
216         compat_map["de"] = "de_DE";
217         compat_map["nl"] = "nl_NL";
218         compat_map["ru"] = "ru_RU";
219         compat_map["pl"] = "pl_PL";
220         compat_map["da"] = "da_DK";
221         compat_map["pt"] = "pt_PT";
222         compat_map["sk"] = "sk_SK";
223         compat_map["cs"] = "cs_CZ";
224         compat_map["uk"] = "uk_UA";
225
226         string lang = config->language().get_value_or ("en_GB");
227         if (compat_map.find (lang) != compat_map.end ()) {
228                 lang = compat_map[lang];
229         }
230
231         checked_set (_language, lang);
232
233         checked_set (_check_for_updates, config->check_for_updates ());
234         checked_set (_check_for_test_updates, config->check_for_test_updates ());
235
236         checked_set (_sound, config->sound ());
237
238         optional<string> const current_so = get_sound_output ();
239         optional<string> configured_so;
240
241         if (config->sound_output()) {
242                 configured_so = config->sound_output().get();
243         } else {
244                 /* No configured output means we should use the default */
245                 RtAudio audio (DCPOMATIC_RTAUDIO_API);
246                 try {
247                         configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
248                 } catch (RtAudioError& e) {
249                         /* Probably no audio devices at all */
250                 }
251         }
252
253         if (configured_so && current_so != configured_so) {
254                 /* Update _sound_output with the configured value */
255                 unsigned int i = 0;
256                 while (i < _sound_output->GetCount()) {
257                         if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
258                                 _sound_output->SetSelection (i);
259                                 break;
260                         }
261                         ++i;
262                 }
263         }
264
265         RtAudio audio (DCPOMATIC_RTAUDIO_API);
266
267         map<int, wxString> apis;
268         apis[RtAudio::MACOSX_CORE]    = _("CoreAudio");
269         apis[RtAudio::WINDOWS_ASIO]   = _("ASIO");
270         apis[RtAudio::WINDOWS_DS]     = _("Direct Sound");
271         apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
272         apis[RtAudio::UNIX_JACK]      = _("JACK");
273         apis[RtAudio::LINUX_ALSA]     = _("ALSA");
274         apis[RtAudio::LINUX_PULSE]    = _("PulseAudio");
275         apis[RtAudio::LINUX_OSS]      = _("OSS");
276         apis[RtAudio::RTAUDIO_DUMMY]  = _("Dummy");
277
278         int channels = 0;
279         if (configured_so) {
280                 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
281                         RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
282                         if (info.name == *configured_so && info.outputChannels > 0) {
283                                 channels = info.outputChannels;
284                         }
285                 }
286         }
287
288         _sound_output_details->SetLabel (
289                 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
290                 );
291
292         setup_sensitivity ();
293 }
294
295 void
296 GeneralPage::setup_sensitivity ()
297 {
298         _language->Enable (_set_language->GetValue ());
299         _check_for_test_updates->Enable (_check_for_updates->GetValue ());
300         _sound_output->Enable (_sound->GetValue ());
301 }
302
303 /** @return Currently-selected preview sound output in the dialogue */
304 optional<string>
305 GeneralPage::get_sound_output ()
306 {
307         int const sel = _sound_output->GetSelection ();
308         if (sel == wxNOT_FOUND) {
309                 return optional<string> ();
310         }
311
312         return wx_to_std (_sound_output->GetString (sel));
313 }
314
315 void
316 GeneralPage::set_language_changed ()
317 {
318         setup_sensitivity ();
319         if (_set_language->GetValue ()) {
320                 language_changed ();
321         } else {
322                 Config::instance()->unset_language ();
323         }
324 }
325
326 void
327 GeneralPage::language_changed ()
328 {
329         int const sel = _language->GetSelection ();
330         if (sel != -1) {
331                 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
332         } else {
333                 Config::instance()->unset_language ();
334         }
335 }
336
337 void
338 GeneralPage::check_for_updates_changed ()
339 {
340         Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
341 }
342
343 void
344 GeneralPage::check_for_test_updates_changed ()
345 {
346         Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
347 }
348
349 void
350 GeneralPage::sound_changed ()
351 {
352         Config::instance()->set_sound (_sound->GetValue ());
353 }
354
355 void
356 GeneralPage::sound_output_changed ()
357 {
358         RtAudio audio (DCPOMATIC_RTAUDIO_API);
359         optional<string> const so = get_sound_output();
360         if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
361                 Config::instance()->unset_sound_output ();
362         } else {
363                 Config::instance()->set_sound_output (*so);
364         }
365 }
366
367 CertificateChainEditor::CertificateChainEditor (
368         wxWindow* parent,
369         wxString title,
370         int border,
371         function<void (shared_ptr<dcp::CertificateChain>)> set,
372         function<shared_ptr<const dcp::CertificateChain> (void)> get,
373         function<bool (void)> nag_alter
374         )
375         : wxDialog (parent, wxID_ANY, title)
376         , _set (set)
377         , _get (get)
378         , _nag_alter (nag_alter)
379 {
380         wxFont subheading_font (*wxNORMAL_FONT);
381         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
382
383         _sizer = new wxBoxSizer (wxVERTICAL);
384
385         {
386                 wxStaticText* m = new StaticText (this, title);
387                 m->SetFont (subheading_font);
388                 _sizer->Add (m, 0, wxALL, border);
389         }
390
391         wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
392         _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
393
394         _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
395
396         {
397                 wxListItem ip;
398                 ip.SetId (0);
399                 ip.SetText (_("Type"));
400                 ip.SetWidth (100);
401                 _certificates->InsertColumn (0, ip);
402         }
403
404         {
405                 wxListItem ip;
406                 ip.SetId (1);
407                 ip.SetText (_("Thumbprint"));
408                 ip.SetWidth (340);
409
410                 wxFont font = ip.GetFont ();
411                 font.SetFamily (wxFONTFAMILY_TELETYPE);
412                 ip.SetFont (font);
413
414                 _certificates->InsertColumn (1, ip);
415         }
416
417         certificates_sizer->Add (_certificates, 1, wxEXPAND);
418
419         {
420                 wxSizer* s = new wxBoxSizer (wxVERTICAL);
421                 _add_certificate = new Button (this, _("Add..."));
422                 s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
423                 _remove_certificate = new Button (this, _("Remove"));
424                 s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
425                 _export_certificate = new Button (this, _("Export"));
426                 s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
427                 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
428         }
429
430         wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
431         _sizer->Add (table, 1, wxALL | wxEXPAND, border);
432         int r = 0;
433
434         add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
435         _private_key = new StaticText (this, wxT(""));
436         wxFont font = _private_key->GetFont ();
437         font.SetFamily (wxFONTFAMILY_TELETYPE);
438         _private_key->SetFont (font);
439         table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
440         _import_private_key = new Button (this, _("Import..."));
441         table->Add (_import_private_key, wxGBPosition (r, 2));
442         _export_private_key = new Button (this, _("Export..."));
443         table->Add (_export_private_key, wxGBPosition (r, 3));
444         ++r;
445
446         _button_sizer = new wxBoxSizer (wxHORIZONTAL);
447         _remake_certificates = new Button (this, _("Re-make certificates and key..."));
448         _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
449         _export_chain = new Button (this, _("Export chain..."));
450         _button_sizer->Add (_export_chain, 1, wxRIGHT, border);
451         table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
452         ++r;
453
454         _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
455         font = *wxSMALL_FONT;
456         font.SetWeight (wxFONTWEIGHT_BOLD);
457         _private_key_bad->SetFont (font);
458         table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
459         ++r;
460
461         _add_certificate->Bind     (wxEVT_BUTTON,       bind (&CertificateChainEditor::add_certificate, this));
462         _remove_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::remove_certificate, this));
463         _export_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_certificate, this));
464         _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   bind (&CertificateChainEditor::update_sensitivity, this));
465         _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
466         _remake_certificates->Bind (wxEVT_BUTTON,       bind (&CertificateChainEditor::remake_certificates, this));
467         _export_chain->Bind        (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_chain, this));
468         _import_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::import_private_key, this));
469         _export_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_private_key, this));
470
471         wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
472         if (buttons) {
473                 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
474         }
475
476         SetSizerAndFit (_sizer);
477
478         update_certificate_list ();
479         update_private_key ();
480         update_sensitivity ();
481 }
482
483 void
484 CertificateChainEditor::add_button (wxWindow* button)
485 {
486         _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
487         _sizer->Layout ();
488 }
489
490 void
491 CertificateChainEditor::add_certificate ()
492 {
493         wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
494
495         if (d->ShowModal() == wxID_OK) {
496                 try {
497                         dcp::Certificate c;
498                         string extra;
499                         try {
500                                 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
501                         } catch (boost::filesystem::filesystem_error& e) {
502                                 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
503                                 d->Destroy ();
504                                 return;
505                         }
506
507                         if (!extra.empty ()) {
508                                 message_dialog (
509                                         this,
510                                         _("This file contains other certificates (or other data) after its first certificate. "
511                                           "Only the first certificate will be used.")
512                                         );
513                         }
514                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
515                         chain->add (c);
516                         if (!chain->chain_valid ()) {
517                                 error_dialog (
518                                         this,
519                                         _("Adding this certificate would make the chain inconsistent, so it will not be added. "
520                                           "Add certificates in order from root to intermediate to leaf.")
521                                         );
522                                 chain->remove (c);
523                         } else {
524                                 _set (chain);
525                                 update_certificate_list ();
526                         }
527                 } catch (dcp::MiscError& e) {
528                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
529                 }
530         }
531
532         d->Destroy ();
533
534         update_sensitivity ();
535 }
536
537 void
538 CertificateChainEditor::remove_certificate ()
539 {
540         if (_nag_alter()) {
541                 /* Cancel was clicked */
542                 return;
543         }
544
545         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
546         if (i == -1) {
547                 return;
548         }
549
550         _certificates->DeleteItem (i);
551         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
552         chain->remove (i);
553         _set (chain);
554
555         update_sensitivity ();
556 }
557
558 void
559 CertificateChainEditor::export_certificate ()
560 {
561         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
562         if (i == -1) {
563                 return;
564         }
565
566         wxFileDialog* d = new wxFileDialog (
567                 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
568                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
569                 );
570
571         dcp::CertificateChain::List all = _get()->root_to_leaf ();
572         dcp::CertificateChain::List::iterator j = all.begin ();
573         for (int k = 0; k < i; ++k) {
574                 ++j;
575         }
576
577         if (d->ShowModal () == wxID_OK) {
578                 boost::filesystem::path path (wx_to_std(d->GetPath()));
579                 FILE* f = fopen_boost (path, "w");
580                 if (!f) {
581                         throw OpenFileError (path, errno, false);
582                 }
583
584                 string const s = j->certificate (true);
585                 checked_fwrite (s.c_str(), s.length(), f, path);
586                 fclose (f);
587         }
588         d->Destroy ();
589 }
590
591 void
592 CertificateChainEditor::export_chain ()
593 {
594         wxFileDialog* d = new wxFileDialog (
595                 this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
596                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
597                 );
598
599         if (d->ShowModal () == wxID_OK) {
600                 boost::filesystem::path path (wx_to_std(d->GetPath()));
601                 FILE* f = fopen_boost (path, "w");
602                 if (!f) {
603                         throw OpenFileError (path, errno, false);
604                 }
605
606                 string const s = _get()->chain();
607                 checked_fwrite (s.c_str(), s.length(), f, path);
608                 fclose (f);
609         }
610
611         d->Destroy ();
612 }
613
614 void
615 CertificateChainEditor::update_certificate_list ()
616 {
617         _certificates->DeleteAllItems ();
618         size_t n = 0;
619         dcp::CertificateChain::List certs = _get()->root_to_leaf ();
620         BOOST_FOREACH (dcp::Certificate const & i, certs) {
621                 wxListItem item;
622                 item.SetId (n);
623                 _certificates->InsertItem (item);
624                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
625
626                 if (n == 0) {
627                         _certificates->SetItem (n, 0, _("Root"));
628                 } else if (n == (certs.size() - 1)) {
629                         _certificates->SetItem (n, 0, _("Leaf"));
630                 } else {
631                         _certificates->SetItem (n, 0, _("Intermediate"));
632                 }
633
634                 ++n;
635         }
636
637         static wxColour normal = _private_key_bad->GetForegroundColour ();
638
639         if (_get()->private_key_valid()) {
640                 _private_key_bad->Hide ();
641                 _private_key_bad->SetForegroundColour (normal);
642         } else {
643                 _private_key_bad->Show ();
644                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
645         }
646 }
647
648 void
649 CertificateChainEditor::remake_certificates ()
650 {
651         shared_ptr<const dcp::CertificateChain> chain = _get();
652
653         string subject_organization_name;
654         string subject_organizational_unit_name;
655         string root_common_name;
656         string intermediate_common_name;
657         string leaf_common_name;
658
659         dcp::CertificateChain::List all = chain->root_to_leaf ();
660
661         if (all.size() >= 1) {
662                 /* Have a root */
663                 subject_organization_name = chain->root().subject_organization_name ();
664                 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
665                 root_common_name = chain->root().subject_common_name ();
666         }
667
668         if (all.size() >= 2) {
669                 /* Have a leaf */
670                 leaf_common_name = chain->leaf().subject_common_name ();
671         }
672
673         if (all.size() >= 3) {
674                 /* Have an intermediate */
675                 dcp::CertificateChain::List::iterator i = all.begin ();
676                 ++i;
677                 intermediate_common_name = i->subject_common_name ();
678         }
679
680         if (_nag_alter()) {
681                 /* Cancel was clicked */
682                 return;
683         }
684
685         MakeChainDialog* d = new MakeChainDialog (
686                 this,
687                 subject_organization_name,
688                 subject_organizational_unit_name,
689                 root_common_name,
690                 intermediate_common_name,
691                 leaf_common_name
692                 );
693
694         if (d->ShowModal () == wxID_OK) {
695                 _set (
696                         shared_ptr<dcp::CertificateChain> (
697                                 new dcp::CertificateChain (
698                                         openssl_path (),
699                                         d->organisation (),
700                                         d->organisational_unit (),
701                                         d->root_common_name (),
702                                         d->intermediate_common_name (),
703                                         d->leaf_common_name ()
704                                         )
705                                 )
706                         );
707
708                 update_certificate_list ();
709                 update_private_key ();
710         }
711
712         d->Destroy ();
713 }
714
715 void
716 CertificateChainEditor::update_sensitivity ()
717 {
718         /* We can only remove the leaf certificate */
719         _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
720         _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
721 }
722
723 void
724 CertificateChainEditor::update_private_key ()
725 {
726         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
727         _sizer->Layout ();
728 }
729
730 void
731 CertificateChainEditor::import_private_key ()
732 {
733         wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
734
735         if (d->ShowModal() == wxID_OK) {
736                 try {
737                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
738                         if (boost::filesystem::file_size (p) > 8192) {
739                                 error_dialog (
740                                         this,
741                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
742                                         );
743                                 return;
744                         }
745
746                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
747                         chain->set_key (dcp::file_to_string (p));
748                         _set (chain);
749                         update_private_key ();
750                 } catch (dcp::MiscError& e) {
751                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
752                 }
753         }
754
755         d->Destroy ();
756
757         update_sensitivity ();
758 }
759
760 void
761 CertificateChainEditor::export_private_key ()
762 {
763         optional<string> key = _get()->key();
764         if (!key) {
765                 return;
766         }
767
768         wxFileDialog* d = new wxFileDialog (
769                 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
770                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
771                 );
772
773         if (d->ShowModal () == wxID_OK) {
774                 boost::filesystem::path path (wx_to_std(d->GetPath()));
775                 FILE* f = fopen_boost (path, "w");
776                 if (!f) {
777                         throw OpenFileError (path, errno, false);
778                 }
779
780                 string const s = _get()->key().get ();
781                 checked_fwrite (s.c_str(), s.length(), f, path);
782                 fclose (f);
783         }
784         d->Destroy ();
785 }
786
787 wxString
788 KeysPage::GetName () const
789 {
790         return _("Keys");
791 }
792
793 void
794 KeysPage::setup ()
795 {
796         wxFont subheading_font (*wxNORMAL_FONT);
797         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
798
799         wxSizer* sizer = _panel->GetSizer();
800
801         {
802                 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
803                 m->SetFont (subheading_font);
804                 sizer->Add (m, 0, wxALL, _border);
805         }
806
807         wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
808         sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
809         wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
810         sizer->Add (export_settings, 0, wxLEFT, _border);
811         wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
812         sizer->Add (import_settings, 0, wxLEFT, _border);
813         wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
814         sizer->Add (decryption_advanced, 0, wxALL, _border);
815
816         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
817         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
818         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
819         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
820
821         {
822                 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
823                 m->SetFont (subheading_font);
824                 sizer->Add (m, 0, wxALL, _border);
825         }
826
827         wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
828         sizer->Add (signing_advanced, 0, wxLEFT, _border);
829         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
830 }
831
832 void
833 KeysPage::decryption_advanced ()
834 {
835         CertificateChainEditor* c = new CertificateChainEditor (
836                 _panel, _("Decrypting KDMs"), _border,
837                 bind (&Config::set_decryption_chain, Config::instance (), _1),
838                 bind (&Config::decryption_chain, Config::instance ()),
839                 bind (&KeysPage::nag_alter_decryption_chain, this)
840                 );
841
842         c->ShowModal();
843 }
844
845 void
846 KeysPage::signing_advanced ()
847 {
848         CertificateChainEditor* c = new CertificateChainEditor (
849                 _panel, _("Signing DCPs and KDMs"), _border,
850                 bind (&Config::set_signer_chain, Config::instance (), _1),
851                 bind (&Config::signer_chain, Config::instance ()),
852                 bind (&do_nothing)
853                 );
854
855         c->ShowModal();
856 }
857
858 void
859 KeysPage::export_decryption_chain_and_key ()
860 {
861         wxFileDialog* d = new wxFileDialog (
862                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
863                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
864                 );
865
866         if (d->ShowModal () == wxID_OK) {
867                 boost::filesystem::path path (wx_to_std(d->GetPath()));
868                 FILE* f = fopen_boost (path, "w");
869                 if (!f) {
870                         throw OpenFileError (path, errno, false);
871                 }
872
873                 string const chain = Config::instance()->decryption_chain()->chain();
874                 checked_fwrite (chain.c_str(), chain.length(), f, path);
875                 optional<string> const key = Config::instance()->decryption_chain()->key();
876                 DCPOMATIC_ASSERT (key);
877                 checked_fwrite (key->c_str(), key->length(), f, path);
878                 fclose (f);
879         }
880         d->Destroy ();
881
882 }
883
884 void
885 KeysPage::import_decryption_chain_and_key ()
886 {
887         if (NagDialog::maybe_nag (
888                     _panel,
889                     Config::NAG_IMPORT_DECRYPTION_CHAIN,
890                     _("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!"),
891                     true
892                     )) {
893                 return;
894         }
895
896         wxFileDialog* d = new wxFileDialog (
897                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
898                 );
899
900         if (d->ShowModal () == wxID_OK) {
901                 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
902
903                 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
904                 if (!f) {
905                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
906                 }
907
908                 string current;
909                 while (!feof (f)) {
910                         char buffer[128];
911                         if (fgets (buffer, 128, f) == 0) {
912                                 break;
913                         }
914                         current += buffer;
915                         if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
916                                 new_chain->add (dcp::Certificate (current));
917                                 current = "";
918                         } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
919                                 new_chain->set_key (current);
920                                 current = "";
921                         }
922                 }
923                 fclose (f);
924
925                 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
926                         Config::instance()->set_decryption_chain (new_chain);
927                 } else {
928                         error_dialog (_panel, _("Invalid DCP-o-matic export file"));
929                 }
930         }
931         d->Destroy ();
932 }
933
934 bool
935 KeysPage::nag_alter_decryption_chain ()
936 {
937         return NagDialog::maybe_nag (
938                 _panel,
939                 Config::NAG_ALTER_DECRYPTION_CHAIN,
940                 _("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!"),
941                 true
942                 );
943 }
944
945 void
946 KeysPage::export_decryption_certificate ()
947 {
948         wxFileDialog* d = new wxFileDialog (
949                 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
950                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
951                 );
952
953         if (d->ShowModal () == wxID_OK) {
954                 boost::filesystem::path path (wx_to_std(d->GetPath()));
955                 FILE* f = fopen_boost (path, "w");
956                 if (!f) {
957                         throw OpenFileError (path, errno, false);
958                 }
959
960                 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
961                 checked_fwrite (s.c_str(), s.length(), f, path);
962                 fclose (f);
963         }
964
965         d->Destroy ();
966 }