summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-03-02 11:59:58 +0100
committerCarl Hetherington <cth@carlh.net>2025-03-02 19:22:59 +0100
commite4537bfa1592674bc4838e4b9b2a72f0adeecc99 (patch)
tree0843f4a71e6ddeb62e2c921a11c71baf141de043 /src
parent16f0bebe211e18ed35b5bd32fd6dcfb915f5e02a (diff)
Move Keys preferences to its own file.
Diffstat (limited to 'src')
-rw-r--r--src/wx/config_dialog.cc222
-rw-r--r--src/wx/config_dialog.h31
-rw-r--r--src/wx/full_config_dialog.cc1
-rw-r--r--src/wx/kdm_config_dialog.cc1
-rw-r--r--src/wx/keys_preferences_page.cc283
-rw-r--r--src/wx/keys_preferences_page.h57
-rw-r--r--src/wx/player_config_dialog.cc1
-rw-r--r--src/wx/wscript1
8 files changed, 344 insertions, 253 deletions
diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc
index 6b5669722..a011dbe15 100644
--- a/src/wx/config_dialog.cc
+++ b/src/wx/config_dialog.cc
@@ -53,13 +53,6 @@ using namespace boost::placeholders;
using namespace dcpomatic::preferences;
-static
-bool
-do_nothing()
-{
- return false;
-}
-
Page::Page(wxSize panel_size, int border)
: _border(border)
, _panel(nullptr)
@@ -333,221 +326,6 @@ GeneralPage::check_for_test_updates_changed()
Config::instance()->set_check_for_test_updates(_check_for_test_updates->GetValue());
}
-wxString
-KeysPage::GetName() const
-{
- return _("Keys");
-}
-
-void
-KeysPage::setup()
-{
- wxFont subheading_font(*wxNORMAL_FONT);
- subheading_font.SetWeight(wxFONTWEIGHT_BOLD);
-
- auto sizer = _panel->GetSizer();
-
- {
- auto m = new StaticText(_panel, _("Decrypting KDMs"));
- m->SetFont(subheading_font);
- sizer->Add(m, 0, wxALL | wxEXPAND, _border);
- }
-
- auto kdm_buttons = new wxBoxSizer(wxVERTICAL);
-
- auto export_decryption_certificate = new Button(_panel, _("Export KDM decryption leaf certificate..."));
- kdm_buttons->Add(export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
- auto export_settings = new Button(_panel, _("Export all KDM decryption settings..."));
- kdm_buttons->Add(export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
- auto import_settings = new Button(_panel, _("Import all KDM decryption settings..."));
- kdm_buttons->Add(import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
- auto decryption_advanced = new Button(_panel, _("Advanced..."));
- kdm_buttons->Add(decryption_advanced, 0);
-
- sizer->Add(kdm_buttons, 0, wxLEFT, _border);
-
- export_decryption_certificate->Bind(wxEVT_BUTTON, bind(&KeysPage::export_decryption_certificate, this));
- export_settings->Bind(wxEVT_BUTTON, bind(&KeysPage::export_decryption_chain_and_key, this));
- import_settings->Bind(wxEVT_BUTTON, bind(&KeysPage::import_decryption_chain_and_key, this));
- decryption_advanced->Bind(wxEVT_BUTTON, bind(&KeysPage::decryption_advanced, this));
-
- {
- auto m = new StaticText(_panel, _("Signing DCPs and KDMs"));
- m->SetFont(subheading_font);
- sizer->Add(m, 0, wxALL | wxEXPAND, _border);
- }
-
- auto signing_buttons = new wxBoxSizer(wxVERTICAL);
-
- auto signing_advanced = new Button(_panel, _("Advanced..."));
- signing_buttons->Add(signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
- auto remake_signing = new Button(_panel, _("Re-make certificates and key..."));
- signing_buttons->Add(remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-
- sizer->Add(signing_buttons, 0, wxLEFT, _border);
-
- signing_advanced->Bind(wxEVT_BUTTON, bind(&KeysPage::signing_advanced, this));
- remake_signing->Bind(wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
-}
-
-
-void
-KeysPage::remake_signing()
-{
- MakeChainDialog dialog(_panel, Config::instance()->signer_chain());
-
- if (dialog.ShowModal() == wxID_OK) {
- Config::instance()->set_signer_chain(dialog.get());
- }
-}
-
-
-void
-KeysPage::decryption_advanced()
-{
- CertificateChainEditor editor(
- _panel, _("Decrypting KDMs"), _border,
- bind(&Config::set_decryption_chain, Config::instance(), _1),
- bind(&Config::decryption_chain, Config::instance()),
- bind(&KeysPage::nag_alter_decryption_chain, this)
- );
-
- editor.ShowModal();
-}
-
-void
-KeysPage::signing_advanced()
-{
- CertificateChainEditor editor(
- _panel, _("Signing DCPs and KDMs"), _border,
- bind(&Config::set_signer_chain, Config::instance(), _1),
- bind(&Config::signer_chain, Config::instance()),
- bind(&do_nothing)
- );
-
- editor.ShowModal();
-}
-
-void
-KeysPage::export_decryption_chain_and_key()
-{
- wxFileDialog dialog(
- _panel, _("Select Export File"), wxEmptyString, wxEmptyString, char_to_wx("DOM files (*.dom)|*.dom"),
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT
- );
-
- if (dialog.ShowModal() != wxID_OK) {
- return;
- }
-
- boost::filesystem::path path(wx_to_std(dialog.GetPath()));
- dcp::File f(path, "w");
- if (!f) {
- throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
- }
-
- auto const chain = Config::instance()->decryption_chain()->chain();
- f.checked_write(chain.c_str(), chain.length());
- auto const key = Config::instance()->decryption_chain()->key();
- DCPOMATIC_ASSERT(key);
- f.checked_write(key->c_str(), key->length());
-}
-
-void
-KeysPage::import_decryption_chain_and_key()
-{
- if (NagDialog::maybe_nag(
- _panel,
- Config::NAG_IMPORT_DECRYPTION_CHAIN,
- _("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!"),
- true
- )) {
- return;
- }
-
- wxFileDialog dialog(
- _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, char_to_wx("DOM files (*.dom)|*.dom")
- );
-
- if (dialog.ShowModal() != wxID_OK) {
- return;
- }
-
- auto new_chain = make_shared<dcp::CertificateChain>();
-
- dcp::File f(wx_to_std(dialog.GetPath()), "r");
- if (!f) {
- throw OpenFileError(f.path(), f.open_error(), OpenFileError::WRITE);
- }
-
- string current;
- while (!f.eof()) {
- char buffer[128];
- if (f.gets(buffer, 128) == 0) {
- break;
- }
- current += buffer;
- if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
- new_chain->add(dcp::Certificate(current));
- current = "";
- } else if (strncmp(buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
- new_chain->set_key(current);
- current = "";
- }
- }
-
- if (new_chain->chain_valid() && new_chain->private_key_valid()) {
- Config::instance()->set_decryption_chain(new_chain);
- } else {
- error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s export file")));
- }
-}
-
-bool
-KeysPage::nag_alter_decryption_chain()
-{
- return NagDialog::maybe_nag(
- _panel,
- Config::NAG_ALTER_DECRYPTION_CHAIN,
- _("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!"),
- true
- );
-}
-
-void
-KeysPage::export_decryption_certificate()
-{
- auto config = Config::instance();
- wxString default_name = char_to_wx("dcpomatic");
- if (!config->dcp_creator().empty()) {
- default_name += char_to_wx("_") + std_to_wx(careful_string_filter(config->dcp_creator()));
- }
- if (!config->dcp_issuer().empty()) {
- default_name += char_to_wx("_") + std_to_wx(careful_string_filter(config->dcp_issuer()));
- }
- default_name += char_to_wx("_kdm_decryption_cert.pem");
-
- wxFileDialog dialog(
- _panel, _("Select Certificate File"), wxEmptyString, default_name, char_to_wx("PEM files (*.pem)|*.pem"),
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT
- );
-
- if (dialog.ShowModal() != wxID_OK) {
- return;
- }
-
- boost::filesystem::path path(wx_to_std(dialog.GetPath()));
- if (path.extension() != ".pem") {
- path += ".pem";
- }
- dcp::File f(path, "w");
- if (!f) {
- throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
- }
-
- auto const s = Config::instance()->decryption_chain()->leaf().certificate(true);
- f.checked_write(s.c_str(), s.length());
-}
wxString
SoundPage::GetName() const
diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h
index 77b8b2b5e..2f1a363b7 100644
--- a/src/wx/config_dialog.h
+++ b/src/wx/config_dialog.h
@@ -121,37 +121,6 @@ private:
};
-class KeysPage : public Page
-{
-public:
- KeysPage(wxSize panel_size, int border)
- : Page(panel_size, border)
- {}
-
- wxString GetName() const override;
-
-#ifdef DCPOMATIC_OSX
- wxBitmap GetLargeIcon() const override
- {
- return wxBitmap(icon_path("keys"), wxBITMAP_TYPE_PNG);
- }
-#endif
-
-private:
-
- void setup() override;
-
- void export_decryption_certificate();
- void config_changed() override {}
- bool nag_alter_decryption_chain();
- void decryption_advanced();
- void signing_advanced();
- void export_decryption_chain_and_key();
- void import_decryption_chain_and_key();
- void remake_signing();
-};
-
-
class SoundPage : public Page
{
public:
diff --git a/src/wx/full_config_dialog.cc b/src/wx/full_config_dialog.cc
index 016fb15f9..821390022 100644
--- a/src/wx/full_config_dialog.cc
+++ b/src/wx/full_config_dialog.cc
@@ -38,6 +38,7 @@
#include "full_config_dialog.h"
#include "kdm_choice.h"
#include "kdm_email_preferences_page.h"
+#include "keys_preferences_page.h"
#include "language_tag_widget.h"
#include "make_chain_dialog.h"
#include "nag_dialog.h"
diff --git a/src/wx/kdm_config_dialog.cc b/src/wx/kdm_config_dialog.cc
index 1e9d3a0f4..528ee0e52 100644
--- a/src/wx/kdm_config_dialog.cc
+++ b/src/wx/kdm_config_dialog.cc
@@ -28,6 +28,7 @@
#include "config_dialog.h"
#include "email_preferences_page.h"
#include "kdm_email_preferences_page.h"
+#include "keys_preferences_page.h"
#include "file_picker_ctrl.h"
#include "static_text.h"
#include "wx_variant.h"
diff --git a/src/wx/keys_preferences_page.cc b/src/wx/keys_preferences_page.cc
new file mode 100644
index 000000000..4a0c8bc76
--- /dev/null
+++ b/src/wx/keys_preferences_page.cc
@@ -0,0 +1,283 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "certificate_chain_editor.h"
+#include "dcpomatic_button.h"
+#include "nag_dialog.h"
+#include "keys_preferences_page.h"
+#include "static_text.h"
+#include "wx_variant.h"
+#include "lib/exceptions.h"
+#include "lib/util.h"
+#include <dcp/file.h>
+
+
+
+using std::make_shared;
+using std::string;
+using boost::bind;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+using namespace dcpomatic::preferences;
+
+
+KeysPage::KeysPage(wxSize panel_size, int border)
+ : Page(panel_size, border)
+{
+
+}
+
+
+#ifdef DCPOMATIC_OSX
+wxBitmap
+KeysPage::GetLargeIcon() const
+{
+ return wxBitmap(icon_path("keys"), wxBITMAP_TYPE_PNG);
+}
+#endif
+
+
+wxString
+KeysPage::GetName() const
+{
+ return _("Keys");
+}
+
+void
+KeysPage::setup()
+{
+ wxFont subheading_font(*wxNORMAL_FONT);
+ subheading_font.SetWeight(wxFONTWEIGHT_BOLD);
+
+ auto sizer = _panel->GetSizer();
+
+ {
+ auto m = new StaticText(_panel, _("Decrypting KDMs"));
+ m->SetFont(subheading_font);
+ sizer->Add(m, 0, wxALL | wxEXPAND, _border);
+ }
+
+ auto kdm_buttons = new wxBoxSizer(wxVERTICAL);
+
+ auto export_decryption_certificate = new Button(_panel, _("Export KDM decryption leaf certificate..."));
+ kdm_buttons->Add(export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+ auto export_settings = new Button(_panel, _("Export all KDM decryption settings..."));
+ kdm_buttons->Add(export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+ auto import_settings = new Button(_panel, _("Import all KDM decryption settings..."));
+ kdm_buttons->Add(import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+ auto decryption_advanced = new Button(_panel, _("Advanced..."));
+ kdm_buttons->Add(decryption_advanced, 0);
+
+ sizer->Add(kdm_buttons, 0, wxLEFT, _border);
+
+ export_decryption_certificate->Bind(wxEVT_BUTTON, bind(&KeysPage::export_decryption_certificate, this));
+ export_settings->Bind(wxEVT_BUTTON, bind(&KeysPage::export_decryption_chain_and_key, this));
+ import_settings->Bind(wxEVT_BUTTON, bind(&KeysPage::import_decryption_chain_and_key, this));
+ decryption_advanced->Bind(wxEVT_BUTTON, bind(&KeysPage::decryption_advanced, this));
+
+ {
+ auto m = new StaticText(_panel, _("Signing DCPs and KDMs"));
+ m->SetFont(subheading_font);
+ sizer->Add(m, 0, wxALL | wxEXPAND, _border);
+ }
+
+ auto signing_buttons = new wxBoxSizer(wxVERTICAL);
+
+ auto signing_advanced = new Button(_panel, _("Advanced..."));
+ signing_buttons->Add(signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+ auto remake_signing = new Button(_panel, _("Re-make certificates and key..."));
+ signing_buttons->Add(remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+
+ sizer->Add(signing_buttons, 0, wxLEFT, _border);
+
+ signing_advanced->Bind(wxEVT_BUTTON, bind(&KeysPage::signing_advanced, this));
+ remake_signing->Bind(wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
+}
+
+
+void
+KeysPage::remake_signing()
+{
+ MakeChainDialog dialog(_panel, Config::instance()->signer_chain());
+
+ if (dialog.ShowModal() == wxID_OK) {
+ Config::instance()->set_signer_chain(dialog.get());
+ }
+}
+
+
+void
+KeysPage::decryption_advanced()
+{
+ CertificateChainEditor editor(
+ _panel, _("Decrypting KDMs"), _border,
+ bind(&Config::set_decryption_chain, Config::instance(), _1),
+ bind(&Config::decryption_chain, Config::instance()),
+ bind(&KeysPage::nag_alter_decryption_chain, this)
+ );
+
+ editor.ShowModal();
+}
+
+
+static
+bool
+do_nothing()
+{
+ return false;
+}
+
+
+void
+KeysPage::signing_advanced()
+{
+ CertificateChainEditor editor(
+ _panel, _("Signing DCPs and KDMs"), _border,
+ bind(&Config::set_signer_chain, Config::instance(), _1),
+ bind(&Config::signer_chain, Config::instance()),
+ bind(&do_nothing)
+ );
+
+ editor.ShowModal();
+}
+
+void
+KeysPage::export_decryption_chain_and_key()
+{
+ wxFileDialog dialog(
+ _panel, _("Select Export File"), wxEmptyString, wxEmptyString, char_to_wx("DOM files (*.dom)|*.dom"),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if (dialog.ShowModal() != wxID_OK) {
+ return;
+ }
+
+ boost::filesystem::path path(wx_to_std(dialog.GetPath()));
+ dcp::File f(path, "w");
+ if (!f) {
+ throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
+ }
+
+ auto const chain = Config::instance()->decryption_chain()->chain();
+ f.checked_write(chain.c_str(), chain.length());
+ auto const key = Config::instance()->decryption_chain()->key();
+ DCPOMATIC_ASSERT(key);
+ f.checked_write(key->c_str(), key->length());
+}
+
+void
+KeysPage::import_decryption_chain_and_key()
+{
+ if (NagDialog::maybe_nag(
+ _panel,
+ Config::NAG_IMPORT_DECRYPTION_CHAIN,
+ _("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!"),
+ true
+ )) {
+ return;
+ }
+
+ wxFileDialog dialog(
+ _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, char_to_wx("DOM files (*.dom)|*.dom")
+ );
+
+ if (dialog.ShowModal() != wxID_OK) {
+ return;
+ }
+
+ auto new_chain = make_shared<dcp::CertificateChain>();
+
+ dcp::File f(wx_to_std(dialog.GetPath()), "r");
+ if (!f) {
+ throw OpenFileError(f.path(), f.open_error(), OpenFileError::WRITE);
+ }
+
+ string current;
+ while (!f.eof()) {
+ char buffer[128];
+ if (f.gets(buffer, 128) == 0) {
+ break;
+ }
+ current += buffer;
+ if (strncmp(buffer, "-----END CERTIFICATE-----", 25) == 0) {
+ new_chain->add(dcp::Certificate(current));
+ current = "";
+ } else if (strncmp(buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
+ new_chain->set_key(current);
+ current = "";
+ }
+ }
+
+ if (new_chain->chain_valid() && new_chain->private_key_valid()) {
+ Config::instance()->set_decryption_chain(new_chain);
+ } else {
+ error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s export file")));
+ }
+}
+
+bool
+KeysPage::nag_alter_decryption_chain()
+{
+ return NagDialog::maybe_nag(
+ _panel,
+ Config::NAG_ALTER_DECRYPTION_CHAIN,
+ _("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!"),
+ true
+ );
+}
+
+void
+KeysPage::export_decryption_certificate()
+{
+ auto config = Config::instance();
+ wxString default_name = char_to_wx("dcpomatic");
+ if (!config->dcp_creator().empty()) {
+ default_name += char_to_wx("_") + std_to_wx(careful_string_filter(config->dcp_creator()));
+ }
+ if (!config->dcp_issuer().empty()) {
+ default_name += char_to_wx("_") + std_to_wx(careful_string_filter(config->dcp_issuer()));
+ }
+ default_name += char_to_wx("_kdm_decryption_cert.pem");
+
+ wxFileDialog dialog(
+ _panel, _("Select Certificate File"), wxEmptyString, default_name, char_to_wx("PEM files (*.pem)|*.pem"),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if (dialog.ShowModal() != wxID_OK) {
+ return;
+ }
+
+ boost::filesystem::path path(wx_to_std(dialog.GetPath()));
+ if (path.extension() != ".pem") {
+ path += ".pem";
+ }
+ dcp::File f(path, "w");
+ if (!f) {
+ throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
+ }
+
+ auto const s = Config::instance()->decryption_chain()->leaf().certificate(true);
+ f.checked_write(s.c_str(), s.length());
+}
+
diff --git a/src/wx/keys_preferences_page.h b/src/wx/keys_preferences_page.h
new file mode 100644
index 000000000..34233e745
--- /dev/null
+++ b/src/wx/keys_preferences_page.h
@@ -0,0 +1,57 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "config_dialog.h"
+
+
+namespace dcpomatic {
+namespace preferences {
+
+
+class KeysPage : public Page
+{
+public:
+ KeysPage(wxSize panel_size, int border);
+
+ wxString GetName() const override;
+
+#ifdef DCPOMATIC_OSX
+ wxBitmap GetLargeIcon() const override;
+#endif
+
+private:
+
+ void setup() override;
+
+ void export_decryption_certificate();
+ void config_changed() override {}
+ bool nag_alter_decryption_chain();
+ void decryption_advanced();
+ void signing_advanced();
+ void export_decryption_chain_and_key();
+ void import_decryption_chain_and_key();
+ void remake_signing();
+};
+
+
+}
+}
+
diff --git a/src/wx/player_config_dialog.cc b/src/wx/player_config_dialog.cc
index 6307ddc8f..3f4c3f864 100644
--- a/src/wx/player_config_dialog.cc
+++ b/src/wx/player_config_dialog.cc
@@ -32,6 +32,7 @@
#include "file_picker_ctrl.h"
#include "filter_dialog.h"
#include "make_chain_dialog.h"
+#include "keys_preferences_page.h"
#include "nag_dialog.h"
#include "name_format_editor.h"
#include "server_dialog.h"
diff --git a/src/wx/wscript b/src/wx/wscript
index 9448c1eaa..ab345f0ea 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -112,6 +112,7 @@ sources = """
kdm_email_preferences_page.cc
kdm_output_panel.cc
kdm_timing_panel.cc
+ keys_preferences_page.cc
language_subtag_panel.cc
language_tag_dialog.cc
language_tag_widget.cc