Support RtAudio >= 6.
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2021 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
22 #include "audio_mapping_view.h"
23 #include "check_box.h"
24 #include "config_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "nag_dialog.h"
27 #include "static_text.h"
28 #include "lib/constants.h"
29 #include <dcp/file.h>
30 #include <dcp/filesystem.h>
31 #include <dcp/raw_convert.h>
32
33
34 using std::function;
35 using std::make_pair;
36 using std::make_shared;
37 using std::map;
38 using std::pair;
39 using std::shared_ptr;
40 using std::string;
41 using std::vector;
42 using boost::bind;
43 using boost::optional;
44 #if BOOST_VERSION >= 106100
45 using namespace boost::placeholders;
46 #endif
47
48
49 static
50 bool
51 do_nothing ()
52 {
53         return false;
54 }
55
56 Page::Page (wxSize panel_size, int border)
57         : _border (border)
58         , _panel (0)
59         , _panel_size (panel_size)
60         , _window_exists (false)
61 {
62         _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
63 }
64
65
66 wxWindow*
67 Page::CreateWindow (wxWindow* parent)
68 {
69         return create_window (parent);
70 }
71
72
73 wxWindow*
74 Page::create_window (wxWindow* parent)
75 {
76         _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
77         auto s = new wxBoxSizer (wxVERTICAL);
78         _panel->SetSizer (s);
79
80         setup ();
81         _window_exists = true;
82         config_changed ();
83
84         _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
85
86         return _panel;
87 }
88
89 void
90 Page::config_changed_wrapper ()
91 {
92         if (_window_exists) {
93                 config_changed ();
94         }
95 }
96
97 void
98 Page::window_destroyed ()
99 {
100         _window_exists = false;
101 }
102
103
104 GeneralPage::GeneralPage (wxSize panel_size, int border)
105         : Page (panel_size, border)
106 {
107
108 }
109
110
111 wxString
112 GeneralPage::GetName () const
113 {
114         return _("General");
115 }
116
117
118 void
119 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
120 {
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("ქართული", "ka_KA"));
134         languages.push_back (make_pair("Nederlands", "nl_NL"));
135         languages.push_back (make_pair("Русский", "ru_RU"));
136         languages.push_back (make_pair("Polski", "pl_PL"));
137         languages.push_back (make_pair("Português europeu", "pt_PT"));
138         languages.push_back (make_pair("Português do Brasil", "pt_BR"));
139         languages.push_back (make_pair("Svenska", "sv_SE"));
140         languages.push_back (make_pair("Slovenščina", "sl_SI"));
141         languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
142         // languages.push_back (make_pair("Türkçe", "tr_TR"));
143         languages.push_back (make_pair("українська мова", "uk_UA"));
144         languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
145         checked_set (_language, languages);
146         table->Add (_language, wxGBPosition (r, 1));
147         ++r;
148
149         auto restart = add_label_to_sizer (
150                 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
151                 );
152         wxFont font = restart->GetFont();
153         font.SetStyle (wxFONTSTYLE_ITALIC);
154         font.SetPointSize (font.GetPointSize() - 1);
155         restart->SetFont (font);
156         ++r;
157
158         _set_language->bind(&GeneralPage::set_language_changed, this);
159         _language->Bind     (wxEVT_CHOICE,   bind (&GeneralPage::language_changed,     this));
160 }
161
162 void
163 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
164 {
165         _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
166         table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
167         ++r;
168
169         _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
170         table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
171         ++r;
172
173         _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
174         _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
175 }
176
177 void
178 GeneralPage::config_changed ()
179 {
180         auto config = Config::instance ();
181
182         checked_set (_set_language, static_cast<bool>(config->language()));
183
184         /* Backwards compatibility of config file */
185
186         map<string, string> compat_map;
187         compat_map["fr"] = "fr_FR";
188         compat_map["it"] = "it_IT";
189         compat_map["es"] = "es_ES";
190         compat_map["sv"] = "sv_SE";
191         compat_map["de"] = "de_DE";
192         compat_map["nl"] = "nl_NL";
193         compat_map["ru"] = "ru_RU";
194         compat_map["pl"] = "pl_PL";
195         compat_map["da"] = "da_DK";
196         compat_map["pt"] = "pt_PT";
197         compat_map["sk"] = "sk_SK";
198         compat_map["cs"] = "cs_CZ";
199         compat_map["uk"] = "uk_UA";
200
201         auto lang = config->language().get_value_or("en_GB");
202         if (compat_map.find(lang) != compat_map.end ()) {
203                 lang = compat_map[lang];
204         }
205
206         checked_set (_language, lang);
207
208         checked_set (_check_for_updates, config->check_for_updates ());
209         checked_set (_check_for_test_updates, config->check_for_test_updates ());
210
211         setup_sensitivity ();
212 }
213
214 void
215 GeneralPage::setup_sensitivity ()
216 {
217         _language->Enable (_set_language->GetValue ());
218         _check_for_test_updates->Enable (_check_for_updates->GetValue ());
219 }
220
221 void
222 GeneralPage::set_language_changed ()
223 {
224         setup_sensitivity ();
225         if (_set_language->GetValue ()) {
226                 language_changed ();
227         } else {
228                 Config::instance()->unset_language ();
229         }
230 }
231
232 void
233 GeneralPage::language_changed ()
234 {
235         int const sel = _language->GetSelection ();
236         if (sel != -1) {
237                 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
238         } else {
239                 Config::instance()->unset_language ();
240         }
241 }
242
243 void
244 GeneralPage::check_for_updates_changed ()
245 {
246         Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
247 }
248
249 void
250 GeneralPage::check_for_test_updates_changed ()
251 {
252         Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
253 }
254
255 CertificateChainEditor::CertificateChainEditor (
256         wxWindow* parent,
257         wxString title,
258         int border,
259         function<void (shared_ptr<dcp::CertificateChain>)> set,
260         function<shared_ptr<const dcp::CertificateChain> (void)> get,
261         function<bool (void)> nag_alter
262         )
263         : wxDialog (parent, wxID_ANY, title)
264         , _set (set)
265         , _get (get)
266         , _nag_alter (nag_alter)
267 {
268         _sizer = new wxBoxSizer (wxVERTICAL);
269
270         auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
271         _sizer->Add (certificates_sizer, 0, wxALL, border);
272
273         _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
274
275         {
276                 wxListItem ip;
277                 ip.SetId (0);
278                 ip.SetText (_("Type"));
279                 ip.SetWidth (100);
280                 _certificates->InsertColumn (0, ip);
281         }
282
283         {
284                 wxListItem ip;
285                 ip.SetId (1);
286                 ip.SetText (_("Thumbprint"));
287                 ip.SetWidth (340);
288
289                 wxFont font = ip.GetFont ();
290                 font.SetFamily (wxFONTFAMILY_TELETYPE);
291                 ip.SetFont (font);
292
293                 _certificates->InsertColumn (1, ip);
294         }
295
296         certificates_sizer->Add (_certificates, 1, wxEXPAND);
297
298         {
299                 auto s = new wxBoxSizer (wxVERTICAL);
300                 _add_certificate = new Button (this, _("Add..."));
301                 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
302                 _remove_certificate = new Button (this, _("Remove"));
303                 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304                 _export_certificate = new Button (this, _("Export certificate..."));
305                 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
306                 _export_chain = new Button (this, _("Export chain..."));
307                 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
308                 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
309         }
310
311         auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
312         _sizer->Add (table, 1, wxALL | wxEXPAND, border);
313         int r = 0;
314
315         add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
316         _private_key = new StaticText (this, wxT(""));
317         wxFont font = _private_key->GetFont ();
318         font.SetFamily (wxFONTFAMILY_TELETYPE);
319         _private_key->SetFont (font);
320         table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
321         _import_private_key = new Button (this, _("Import..."));
322         table->Add (_import_private_key, wxGBPosition (r, 2));
323         _export_private_key = new Button (this, _("Export..."));
324         table->Add (_export_private_key, wxGBPosition (r, 3));
325         ++r;
326
327         _button_sizer = new wxBoxSizer (wxHORIZONTAL);
328         _remake_certificates = new Button (this, _("Re-make certificates and key..."));
329         _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
330         table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
331         ++r;
332
333         _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
334         font = *wxSMALL_FONT;
335         font.SetWeight (wxFONTWEIGHT_BOLD);
336         _private_key_bad->SetFont (font);
337         table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
338         ++r;
339
340         _add_certificate->Bind     (wxEVT_BUTTON,       bind (&CertificateChainEditor::add_certificate, this));
341         _remove_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::remove_certificate, this));
342         _export_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_certificate, this));
343         _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   bind (&CertificateChainEditor::update_sensitivity, this));
344         _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
345         _remake_certificates->Bind (wxEVT_BUTTON,       bind (&CertificateChainEditor::remake_certificates, this));
346         _export_chain->Bind        (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_chain, this));
347         _import_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::import_private_key, this));
348         _export_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_private_key, this));
349
350         auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
351         if (buttons) {
352                 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
353         }
354
355         SetSizerAndFit (_sizer);
356
357         update_certificate_list ();
358         update_private_key ();
359         update_sensitivity ();
360 }
361
362 void
363 CertificateChainEditor::add_button (wxWindow* button)
364 {
365         _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
366         _sizer->Layout ();
367 }
368
369 void
370 CertificateChainEditor::add_certificate ()
371 {
372         auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
373
374         if (d->ShowModal() == wxID_OK) {
375                 try {
376                         dcp::Certificate c;
377                         string extra;
378                         try {
379                                 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
380                         } catch (boost::filesystem::filesystem_error& e) {
381                                 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
382                                 return;
383                         }
384
385                         if (!extra.empty ()) {
386                                 message_dialog (
387                                         this,
388                                         _("This file contains other certificates (or other data) after its first certificate. "
389                                           "Only the first certificate will be used.")
390                                         );
391                         }
392                         auto chain = make_shared<dcp::CertificateChain>(*_get().get());
393                         chain->add (c);
394                         if (!chain->chain_valid ()) {
395                                 error_dialog (
396                                         this,
397                                         _("Adding this certificate would make the chain inconsistent, so it will not be added. "
398                                           "Add certificates in order from root to intermediate to leaf.")
399                                         );
400                                 chain->remove (c);
401                         } else {
402                                 _set (chain);
403                                 update_certificate_list ();
404                         }
405                 } catch (dcp::MiscError& e) {
406                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
407                 }
408         }
409
410         update_sensitivity ();
411 }
412
413 void
414 CertificateChainEditor::remove_certificate ()
415 {
416         if (_nag_alter()) {
417                 /* Cancel was clicked */
418                 return;
419         }
420
421         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
422         if (i == -1) {
423                 return;
424         }
425
426         _certificates->DeleteItem (i);
427         auto chain = make_shared<dcp::CertificateChain>(*_get().get());
428         chain->remove (i);
429         _set (chain);
430
431         update_sensitivity ();
432         update_certificate_list ();
433 }
434
435 void
436 CertificateChainEditor::export_certificate ()
437 {
438         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
439         if (i == -1) {
440                 return;
441         }
442
443         auto all = _get()->root_to_leaf();
444
445         wxString default_name;
446         if (i == 0) {
447                 default_name = "root.pem";
448         } else if (i == static_cast<int>(all.size() - 1)) {
449                 default_name = "leaf.pem";
450         } else {
451                 default_name = "intermediate.pem";
452         }
453
454         auto d = make_wx<wxFileDialog>(
455                 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
456                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
457                 );
458
459         auto j = all.begin ();
460         for (int k = 0; k < i; ++k) {
461                 ++j;
462         }
463
464         if (d->ShowModal() != wxID_OK) {
465                 return;
466         }
467
468         boost::filesystem::path path(wx_to_std(d->GetPath()));
469         if (path.extension() != ".pem") {
470                 path += ".pem";
471         }
472         dcp::File f(path, "w");
473         if (!f) {
474                 throw OpenFileError(path, errno, OpenFileError::WRITE);
475         }
476
477         string const s = j->certificate(true);
478         f.checked_write(s.c_str(), s.length());
479 }
480
481 void
482 CertificateChainEditor::export_chain ()
483 {
484         auto d = make_wx<wxFileDialog>(
485                 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
486                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
487                 );
488
489         if (d->ShowModal() != wxID_OK) {
490                 return;
491         }
492
493         boost::filesystem::path path(wx_to_std(d->GetPath()));
494         if (path.extension() != ".pem") {
495                 path += ".pem";
496         }
497         dcp::File f(path, "w");
498         if (!f) {
499                 throw OpenFileError(path, errno, OpenFileError::WRITE);
500         }
501
502         auto const s = _get()->chain();
503         f.checked_write(s.c_str(), s.length());
504 }
505
506 void
507 CertificateChainEditor::update_certificate_list ()
508 {
509         _certificates->DeleteAllItems ();
510         size_t n = 0;
511         auto certs = _get()->root_to_leaf();
512         for (auto const& i: certs) {
513                 wxListItem item;
514                 item.SetId (n);
515                 _certificates->InsertItem (item);
516                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
517
518                 if (n == 0) {
519                         _certificates->SetItem (n, 0, _("Root"));
520                 } else if (n == (certs.size() - 1)) {
521                         _certificates->SetItem (n, 0, _("Leaf"));
522                 } else {
523                         _certificates->SetItem (n, 0, _("Intermediate"));
524                 }
525
526                 ++n;
527         }
528
529         static wxColour normal = _private_key_bad->GetForegroundColour ();
530
531         if (_get()->private_key_valid()) {
532                 _private_key_bad->Hide ();
533                 _private_key_bad->SetForegroundColour (normal);
534         } else {
535                 _private_key_bad->Show ();
536                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
537         }
538 }
539
540 void
541 CertificateChainEditor::remake_certificates ()
542 {
543         if (_nag_alter()) {
544                 /* Cancel was clicked */
545                 return;
546         }
547
548         auto d = make_wx<MakeChainDialog>(this, _get());
549
550         if (d->ShowModal () == wxID_OK) {
551                 _set (d->get());
552                 update_certificate_list ();
553                 update_private_key ();
554         }
555 }
556
557 void
558 CertificateChainEditor::update_sensitivity ()
559 {
560         /* We can only remove the leaf certificate */
561         _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
562         _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
563 }
564
565 void
566 CertificateChainEditor::update_private_key ()
567 {
568         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
569         _sizer->Layout ();
570 }
571
572 void
573 CertificateChainEditor::import_private_key ()
574 {
575         auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
576
577         if (d->ShowModal() == wxID_OK) {
578                 try {
579                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
580                         if (dcp::filesystem::file_size(p) > 8192) {
581                                 error_dialog (
582                                         this,
583                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
584                                         );
585                                 return;
586                         }
587
588                         auto chain = make_shared<dcp::CertificateChain>(*_get().get());
589                         chain->set_key (dcp::file_to_string (p));
590                         _set (chain);
591                         update_private_key ();
592                 } catch (std::exception& e) {
593                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
594                 }
595         }
596
597         update_sensitivity ();
598 }
599
600 void
601 CertificateChainEditor::export_private_key ()
602 {
603         auto key = _get()->key();
604         if (!key) {
605                 return;
606         }
607
608         auto d = make_wx<wxFileDialog>(
609                 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
610                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
611                 );
612
613         if (d->ShowModal () == wxID_OK) {
614                 boost::filesystem::path path (wx_to_std(d->GetPath()));
615                 if (path.extension() != ".pem") {
616                         path += ".pem";
617                 }
618                 dcp::File f(path, "w");
619                 if (!f) {
620                         throw OpenFileError (path, errno, OpenFileError::WRITE);
621                 }
622
623                 auto const s = _get()->key().get ();
624                 f.checked_write(s.c_str(), s.length());
625         }
626 }
627
628 wxString
629 KeysPage::GetName () const
630 {
631         return _("Keys");
632 }
633
634 void
635 KeysPage::setup ()
636 {
637         wxFont subheading_font (*wxNORMAL_FONT);
638         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
639
640         auto sizer = _panel->GetSizer();
641
642         {
643                 auto m = new StaticText (_panel, _("Decrypting KDMs"));
644                 m->SetFont (subheading_font);
645                 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
646         }
647
648         auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
649
650         auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
651         kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
652         auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
653         kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654         auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
655         kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656         auto decryption_advanced = new Button (_panel, _("Advanced..."));
657         kdm_buttons->Add (decryption_advanced, 0);
658
659         sizer->Add (kdm_buttons, 0, wxLEFT, _border);
660
661         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
662         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
663         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
664         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
665
666         {
667                 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
668                 m->SetFont (subheading_font);
669                 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
670         }
671
672         auto signing_buttons = new wxBoxSizer (wxVERTICAL);
673
674         auto signing_advanced = new Button (_panel, _("Advanced..."));
675         signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
676         auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
677         signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
678
679         sizer->Add (signing_buttons, 0, wxLEFT, _border);
680
681         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
682         remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
683 }
684
685
686 void
687 KeysPage::remake_signing ()
688 {
689         auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
690
691         if (d->ShowModal () == wxID_OK) {
692                 Config::instance()->set_signer_chain(d->get());
693         }
694 }
695
696
697 void
698 KeysPage::decryption_advanced ()
699 {
700         auto c = new CertificateChainEditor (
701                 _panel, _("Decrypting KDMs"), _border,
702                 bind(&Config::set_decryption_chain, Config::instance(), _1),
703                 bind(&Config::decryption_chain, Config::instance()),
704                 bind(&KeysPage::nag_alter_decryption_chain, this)
705                 );
706
707         c->ShowModal();
708 }
709
710 void
711 KeysPage::signing_advanced ()
712 {
713         auto c = new CertificateChainEditor (
714                 _panel, _("Signing DCPs and KDMs"), _border,
715                 bind(&Config::set_signer_chain, Config::instance(), _1),
716                 bind(&Config::signer_chain, Config::instance()),
717                 bind(&do_nothing)
718                 );
719
720         c->ShowModal();
721 }
722
723 void
724 KeysPage::export_decryption_chain_and_key ()
725 {
726         auto d = make_wx<wxFileDialog>(
727                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
728                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
729                 );
730
731         if (d->ShowModal() != wxID_OK) {
732                 return;
733         }
734
735         boost::filesystem::path path(wx_to_std(d->GetPath()));
736         dcp::File f(path, "w");
737         if (!f) {
738                 throw OpenFileError(path, errno, OpenFileError::WRITE);
739         }
740
741         auto const chain = Config::instance()->decryption_chain()->chain();
742         f.checked_write(chain.c_str(), chain.length());
743         auto const key = Config::instance()->decryption_chain()->key();
744         DCPOMATIC_ASSERT(key);
745         f.checked_write(key->c_str(), key->length());
746 }
747
748 void
749 KeysPage::import_decryption_chain_and_key ()
750 {
751         if (NagDialog::maybe_nag (
752                     _panel,
753                     Config::NAG_IMPORT_DECRYPTION_CHAIN,
754                     _("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!"),
755                     true
756                     )) {
757                 return;
758         }
759
760         auto d = make_wx<wxFileDialog>(
761                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
762                 );
763
764         if (d->ShowModal() != wxID_OK) {
765                 return;
766         }
767
768         auto new_chain = make_shared<dcp::CertificateChain>();
769
770         dcp::File f(wx_to_std(d->GetPath()), "r");
771         if (!f) {
772                 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
773         }
774
775         string current;
776         while (!f.eof()) {
777                 char buffer[128];
778                 if (f.gets(buffer, 128) == 0) {
779                         break;
780                 }
781                 current += buffer;
782                 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
783                         new_chain->add(dcp::Certificate(current));
784                         current = "";
785                 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
786                         new_chain->set_key(current);
787                         current = "";
788                 }
789         }
790
791         if (new_chain->chain_valid() && new_chain->private_key_valid()) {
792                 Config::instance()->set_decryption_chain(new_chain);
793         } else {
794                 error_dialog(_panel, _("Invalid DCP-o-matic export file"));
795         }
796 }
797
798 bool
799 KeysPage::nag_alter_decryption_chain ()
800 {
801         return NagDialog::maybe_nag (
802                 _panel,
803                 Config::NAG_ALTER_DECRYPTION_CHAIN,
804                 _("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!"),
805                 true
806                 );
807 }
808
809 void
810 KeysPage::export_decryption_certificate ()
811 {
812         auto config = Config::instance();
813         wxString default_name = "dcpomatic";
814         if (!config->dcp_creator().empty()) {
815                 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
816         }
817         if (!config->dcp_issuer().empty()) {
818                 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
819         }
820         default_name += wxT("_kdm_decryption_cert.pem");
821
822         auto d = make_wx<wxFileDialog>(
823                 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
824                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
825                 );
826
827         if (d->ShowModal() != wxID_OK) {
828                 return;
829         }
830
831         boost::filesystem::path path(wx_to_std(d->GetPath()));
832         if (path.extension() != ".pem") {
833                 path += ".pem";
834         }
835         dcp::File f(path, "w");
836         if (!f) {
837                 throw OpenFileError(path, errno, OpenFileError::WRITE);
838         }
839
840         auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
841         f.checked_write(s.c_str(), s.length());
842 }
843
844 wxString
845 SoundPage::GetName () const
846 {
847         return _("Sound");
848 }
849
850 void
851 SoundPage::setup ()
852 {
853         auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
854         _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
855
856         int r = 0;
857
858         _sound = new CheckBox (_panel, _("Play sound via"));
859         table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
860         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
861         _sound_output = new wxChoice (_panel, wxID_ANY);
862         s->Add (_sound_output, 0);
863         _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
864         s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
865         table->Add (s, wxGBPosition(r, 1));
866         ++r;
867
868         add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
869         _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
870         table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
871         ++r;
872
873         _reset_to_default = new Button (_panel, _("Reset to default"));
874         table->Add (_reset_to_default, wxGBPosition(r, 1));
875         ++r;
876
877         wxFont font = _sound_output_details->GetFont();
878         font.SetStyle (wxFONTSTYLE_ITALIC);
879         font.SetPointSize (font.GetPointSize() - 1);
880         _sound_output_details->SetFont (font);
881
882         RtAudio audio (DCPOMATIC_RTAUDIO_API);
883 #if (RTAUDIO_VERSION_MAJOR >= 6)
884         for (auto device_id: audio.getDeviceIds()) {
885                 auto dev = audio.getDeviceInfo(device_id);
886                 if (dev.outputChannels > 0) {
887                         _sound_output->Append(std_to_wx(dev.name));
888                 }
889         }
890 #else
891         for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
892                 try {
893                         auto dev = audio.getDeviceInfo (i);
894                         if (dev.probed && dev.outputChannels > 0) {
895                                 _sound_output->Append (std_to_wx (dev.name));
896                         }
897                 } catch (RtAudioError&) {
898                         /* Something went wrong so let's just ignore that device */
899                 }
900         }
901 #endif
902
903         _sound->bind(&SoundPage::sound_changed, this);
904         _sound_output->Bind (wxEVT_CHOICE,   bind(&SoundPage::sound_output_changed, this));
905         _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
906         _reset_to_default->Bind (wxEVT_BUTTON,   bind(&SoundPage::reset_to_default, this));
907 }
908
909 void
910 SoundPage::reset_to_default ()
911 {
912         Config::instance()->set_audio_mapping_to_default ();
913 }
914
915 void
916 SoundPage::map_changed (AudioMapping m)
917 {
918         Config::instance()->set_audio_mapping (m);
919 }
920
921 void
922 SoundPage::sound_changed ()
923 {
924         Config::instance()->set_sound (_sound->GetValue ());
925 }
926
927 void
928 SoundPage::sound_output_changed ()
929 {
930         RtAudio audio (DCPOMATIC_RTAUDIO_API);
931         auto const so = get_sound_output();
932         string default_device;
933 #if (RTAUDIO_VERSION_MAJOR >= 6)
934         default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
935 #else
936         try {
937                 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
938         } catch (RtAudioError&) {
939                 /* Never mind */
940         }
941 #endif
942         if (!so || *so == default_device) {
943                 Config::instance()->unset_sound_output ();
944         } else {
945                 Config::instance()->set_sound_output (*so);
946         }
947 }
948
949 void
950 SoundPage::config_changed ()
951 {
952         auto config = Config::instance ();
953
954         checked_set (_sound, config->sound ());
955
956         auto const current_so = get_sound_output ();
957         optional<string> configured_so;
958
959         if (config->sound_output()) {
960                 configured_so = config->sound_output().get();
961         } else {
962                 /* No configured output means we should use the default */
963                 RtAudio audio (DCPOMATIC_RTAUDIO_API);
964 #if (RTAUDIO_VERSION_MAJOR >= 6)
965                 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
966 #else
967                 try {
968                         configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
969                 } catch (RtAudioError&) {
970                         /* Probably no audio devices at all */
971                 }
972 #endif
973         }
974
975         if (configured_so && current_so != configured_so) {
976                 /* Update _sound_output with the configured value */
977                 unsigned int i = 0;
978                 while (i < _sound_output->GetCount()) {
979                         if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
980                                 _sound_output->SetSelection (i);
981                                 break;
982                         }
983                         ++i;
984                 }
985         }
986
987         RtAudio audio (DCPOMATIC_RTAUDIO_API);
988
989         map<int, wxString> apis;
990         apis[RtAudio::MACOSX_CORE]    = _("CoreAudio");
991         apis[RtAudio::WINDOWS_ASIO]   = _("ASIO");
992         apis[RtAudio::WINDOWS_DS]     = _("Direct Sound");
993         apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
994         apis[RtAudio::UNIX_JACK]      = _("JACK");
995         apis[RtAudio::LINUX_ALSA]     = _("ALSA");
996         apis[RtAudio::LINUX_PULSE]    = _("PulseAudio");
997         apis[RtAudio::LINUX_OSS]      = _("OSS");
998         apis[RtAudio::RTAUDIO_DUMMY]  = _("Dummy");
999
1000         int channels = 0;
1001         if (configured_so) {
1002 #if (RTAUDIO_VERSION_MAJOR >= 6)
1003                 for (auto device_id: audio.getDeviceIds()) {
1004                         auto info = audio.getDeviceInfo(device_id);
1005                         if (info.name == *configured_so && info.outputChannels > 0) {
1006                                 channels = info.outputChannels;
1007                         }
1008                 }
1009 #else
1010                 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1011                         try {
1012                                 auto info = audio.getDeviceInfo(i);
1013                                 if (info.name == *configured_so && info.outputChannels > 0) {
1014                                         channels = info.outputChannels;
1015                                 }
1016                         } catch (RtAudioError&) {
1017                                 /* Never mind */
1018                         }
1019                 }
1020 #endif
1021         }
1022
1023         _sound_output_details->SetLabel (
1024                 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1025                 );
1026
1027         _map->set (Config::instance()->audio_mapping(channels));
1028
1029         vector<NamedChannel> input;
1030         for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1031                 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1032         }
1033         _map->set_input_channels (input);
1034
1035         vector<NamedChannel> output;
1036         for (int i = 0; i < channels; ++i) {
1037                 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1038         }
1039         _map->set_output_channels (output);
1040
1041         setup_sensitivity ();
1042 }
1043
1044 void
1045 SoundPage::setup_sensitivity ()
1046 {
1047         _sound_output->Enable (_sound->GetValue());
1048 }
1049
1050 /** @return Currently-selected preview sound output in the dialogue */
1051 optional<string>
1052 SoundPage::get_sound_output ()
1053 {
1054         int const sel = _sound_output->GetSelection ();
1055         if (sel == wxNOT_FOUND) {
1056                 return optional<string> ();
1057         }
1058
1059         return wx_to_std (_sound_output->GetString (sel));
1060 }
1061
1062
1063 LocationsPage::LocationsPage (wxSize panel_size, int border)
1064         : Page (panel_size, border)
1065 {
1066
1067 }
1068
1069 wxString
1070 LocationsPage::GetName () const
1071 {
1072         return _("Locations");
1073 }
1074
1075 #ifdef DCPOMATIC_OSX
1076 wxBitmap
1077 LocationsPage::GetLargeIcon () const
1078 {
1079         return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1080 }
1081 #endif
1082
1083 void
1084 LocationsPage::setup ()
1085 {
1086         int r = 0;
1087
1088         auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1089         _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1090
1091         add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1092         _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1093         table->Add (_content_directory, wxGBPosition (r, 1));
1094         ++r;
1095
1096         add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1097         _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1098         table->Add (_playlist_directory, wxGBPosition (r, 1));
1099         ++r;
1100
1101         add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1102         _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1103         table->Add (_kdm_directory, wxGBPosition (r, 1));
1104         ++r;
1105
1106         _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1107         _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1108         _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1109 }
1110
1111 void
1112 LocationsPage::config_changed ()
1113 {
1114         auto config = Config::instance ();
1115
1116         if (config->player_content_directory()) {
1117                 checked_set (_content_directory, *config->player_content_directory());
1118         }
1119         if (config->player_playlist_directory()) {
1120                 checked_set (_playlist_directory, *config->player_playlist_directory());
1121         }
1122         if (config->player_kdm_directory()) {
1123                 checked_set (_kdm_directory, *config->player_kdm_directory());
1124         }
1125 }
1126
1127 void
1128 LocationsPage::content_directory_changed ()
1129 {
1130         Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1131 }
1132
1133 void
1134 LocationsPage::playlist_directory_changed ()
1135 {
1136         Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1137 }
1138
1139 void
1140 LocationsPage::kdm_directory_changed ()
1141 {
1142         Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));
1143 }