swaroop: required monitors checks.
authorCarl Hetherington <cth@carlh.net>
Tue, 9 Oct 2018 18:19:45 +0000 (19:19 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 9 Oct 2018 19:11:33 +0000 (20:11 +0100)
13 files changed:
src/lib/config.cc
src/lib/config.h
src/lib/edid.cc [new file with mode: 0644]
src/lib/edid.h [new file with mode: 0644]
src/lib/monitor_checker.cc [new file with mode: 0644]
src/lib/monitor_checker.h [new file with mode: 0644]
src/lib/wscript
src/tools/dcpomatic_player.cc
src/wx/editable_list.h
src/wx/monitor_dialog.cc [new file with mode: 0644]
src/wx/monitor_dialog.h [new file with mode: 0644]
src/wx/player_config_dialog.cc
src/wx/wscript

index 6e06f78..1d83f0a 100644 (file)
@@ -522,6 +522,9 @@ try
        _player_watermark_theatre = f.optional_string_child("PlayerWatermarkTheatre").get_value_or("");
        _player_watermark_period = f.optional_number_child<int>("PlayerWatermarkPeriod").get_value_or(1);
        _player_watermark_duration = f.optional_number_child<int>("PlayerWatermarkDuration").get_value_or(150);
+       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("RequiredMonitor")) {
+               _required_monitors.push_back(Monitor(i));
+       }
 #endif
 
        /* Replace any cinemas from config.xml with those from the configured file */
@@ -928,6 +931,9 @@ Config::write_config () const
        root->add_child("PlayerWatermarkTheatre")->add_child_text(_player_watermark_theatre);
        root->add_child("PlayerWatermarkPeriod")->add_child_text(raw_convert<string>(_player_watermark_period));
        root->add_child("PlayerWatermarkDuration")->add_child_text(raw_convert<string>(_player_watermark_duration));
+       BOOST_FOREACH (Monitor i, _required_monitors) {
+               i.as_xml(root->add_child("RequiredMonitor"));
+       }
 #endif
 
        try {
index 629828d..a470bf3 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "isdcf_metadata.h"
 #include "types.h"
+#include "edid.h"
 #include <dcp/name_format.h>
 #include <dcp/certificate_chain.h>
 #include <dcp/encrypted_kdm.h>
@@ -513,6 +514,10 @@ public:
        int player_watermark_duration () const {
                return _player_watermark_duration;
        }
+
+       std::vector<Monitor> required_monitors () const {
+               return _required_monitors;
+       }
 #endif
 
        /* SET (mostly) */
@@ -989,6 +994,10 @@ public:
        void set_player_watermark_duration (int milliseconds) {
                maybe_set (_player_watermark_duration, milliseconds);
        }
+
+       void set_required_monitors (std::vector<Monitor> monitors) {
+               maybe_set (_required_monitors, monitors);
+       }
 #endif
 
        void changed (Property p = OTHER);
@@ -1192,6 +1201,7 @@ private:
        int _player_watermark_period;
        /** watermark duration in milliseconds */
        int _player_watermark_duration;
+       std::vector<Monitor> _required_monitors;
 #endif
 
        static int const _current_version;
diff --git a/src/lib/edid.cc b/src/lib/edid.cc
new file mode 100644 (file)
index 0000000..3df65d3
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+    Copyright (C) 2018 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 "compose.hpp"
+#include "edid.h"
+#include <dcp/raw_convert.h>
+#include <libxml++/libxml++.h>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+#include <iostream>
+
+#define EDID_SYS_PATH "/sys/class/drm"
+static uint8_t const edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
+
+using std::vector;
+using std::string;
+using std::cout;
+
+vector<Monitor>
+get_monitors()
+{
+       using namespace boost::filesystem;
+       using namespace boost::algorithm;
+
+       vector<Monitor> monitors;
+
+       int card = 0;
+       while (true) {
+               path card_dir = String::compose("%1/card%2", EDID_SYS_PATH, card);
+               if (!is_directory(card_dir)) {
+                       break;
+               }
+
+               for (directory_iterator i = directory_iterator(card_dir); i != directory_iterator(); ++i) {
+                       if (!starts_with(i->path().filename().string(), String::compose("card%1", card))) {
+                               continue;
+                       }
+
+                       FILE* edid_file = fopen(path(i->path() / "edid").string().c_str(), "r");
+                       if (!edid_file) {
+                               continue;
+                       }
+
+                       uint8_t edid[128];
+                       int const N = fread(edid, 1, sizeof(edid), edid_file);
+                       fclose(edid_file);
+                       if (N != 128) {
+                               continue;
+                       }
+
+                       if (memcmp(edid, edid_header, 8) != 0) {
+                               continue;
+                       }
+
+                       Monitor mon;
+
+                       uint16_t mid = (edid[8] << 8) | edid[9];
+                       mon.manufacturer_id += char(((mid >> 10) & 0x1f) + 'A' - 1);
+                       mon.manufacturer_id += char(((mid >> 5) & 0x1f) + 'A' - 1);
+                       mon.manufacturer_id += char(((mid >> 0) & 0x1f) + 'A' - 1);
+
+                       mon.manufacturer_product_code = (edid[11] << 8) | edid[10];
+
+                       mon.serial_number = (edid[15] << 24) | (edid[14] << 16) | (edid[13] << 8) | edid[12];
+                       mon.week_of_manufacture = edid[16];
+                       mon.year_of_manufacture = edid[17];
+                       monitors.push_back (mon);
+               }
+
+               ++card;
+       }
+
+       return monitors;
+}
+
+Monitor::Monitor ()
+       : manufacturer_product_code (0)
+       , serial_number (0)
+       , week_of_manufacture (0)
+       , year_of_manufacture (0)
+{
+
+}
+
+Monitor::Monitor (cxml::ConstNodePtr node)
+       : manufacturer_id(node->string_child("ManufacturerId"))
+       , manufacturer_product_code(node->number_child<uint32_t>("ManufacturerProductCode"))
+       , serial_number(node->number_child<uint32_t>("SerialNumber"))
+       , week_of_manufacture(node->number_child<int>("WeekOfManufacture"))
+       , year_of_manufacture(node->number_child<int>("YearOfManufacture"))
+
+{
+
+}
+
+void
+Monitor::as_xml (xmlpp::Node* parent) const
+{
+       parent->add_child("ManufacturerId")->add_child_text(manufacturer_id);
+       parent->add_child("ManufacturerProductCode")->add_child_text(dcp::raw_convert<string>(manufacturer_product_code));
+       parent->add_child("SerialNumber")->add_child_text(dcp::raw_convert<string>(serial_number));
+       parent->add_child("WeekOfManufacture")->add_child_text(dcp::raw_convert<string>(week_of_manufacture));
+       parent->add_child("YearOfManufacture")->add_child_text(dcp::raw_convert<string>(year_of_manufacture));
+}
+
+bool
+operator== (Monitor const & a, Monitor const & b)
+{
+       return a.manufacturer_id == b.manufacturer_id &&
+               a.manufacturer_product_code == b.manufacturer_product_code &&
+               a.serial_number == b.serial_number &&
+               a.week_of_manufacture == b.week_of_manufacture &&
+               a.year_of_manufacture == b.year_of_manufacture;
+}
diff --git a/src/lib/edid.h b/src/lib/edid.h
new file mode 100644 (file)
index 0000000..e29c77c
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    Copyright (C) 2018 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/>.
+
+*/
+
+#ifndef DCPOMATIC_EDID_H
+#define DCPOMATIC_EDID_H
+
+#include <libcxml/cxml.h>
+#include <vector>
+#include <string>
+
+namespace xmlpp {
+       class Node;
+}
+
+class Monitor
+{
+public:
+       Monitor ();
+       Monitor (cxml::ConstNodePtr node);
+       void as_xml (xmlpp::Node* parent) const;
+
+       std::string manufacturer_id;
+       uint16_t manufacturer_product_code;
+       uint32_t serial_number;
+       uint8_t week_of_manufacture;
+       uint8_t year_of_manufacture;
+};
+
+bool operator== (Monitor const & a, Monitor const & b);
+
+extern std::vector<Monitor> get_monitors();
+
+#endif
diff --git a/src/lib/monitor_checker.cc b/src/lib/monitor_checker.cc
new file mode 100644 (file)
index 0000000..c506cd0
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+    Copyright (C) 2018 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 "monitor_checker.h"
+#include "config.h"
+#include "cross.h"
+
+using boost::bind;
+using boost::ref;
+
+MonitorChecker* MonitorChecker::_instance = 0;
+
+MonitorChecker::MonitorChecker ()
+       : _thread (0)
+       , _terminate (false)
+       , _ok (true)
+{
+
+}
+
+void
+MonitorChecker::run ()
+{
+       _thread = new boost::thread (boost::bind (&MonitorChecker::thread, this));
+}
+
+MonitorChecker::~MonitorChecker ()
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _terminate = true;
+       }
+
+       if (_thread) {
+               /* Ideally this would be a DCPOMATIC_ASSERT(_thread->joinable()) but we
+                  can't throw exceptions from a destructor.
+               */
+               _thread->interrupt ();
+               try {
+                       if (_thread->joinable ()) {
+                               _thread->join ();
+                       }
+               } catch (boost::thread_interrupted& e) {
+                       /* No problem */
+               }
+       }
+       delete _thread;
+}
+
+void
+MonitorChecker::thread ()
+{
+       while (true) {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_terminate) {
+                       break;
+               }
+
+               bool const was_ok = _ok;
+               _ok = Config::instance()->required_monitors().empty() || get_monitors() == Config::instance()->required_monitors();
+               if (was_ok != _ok) {
+                       emit (bind(boost::ref(StateChanged)));
+               }
+
+               lm.unlock ();
+               dcpomatic_sleep (60);
+       }
+}
+
+bool
+MonitorChecker::ok () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       return _ok;
+}
+
+MonitorChecker *
+MonitorChecker::instance ()
+{
+       if (!_instance) {
+               _instance = new MonitorChecker ();
+       }
+
+       return _instance;
+}
diff --git a/src/lib/monitor_checker.h b/src/lib/monitor_checker.h
new file mode 100644 (file)
index 0000000..4f856f4
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+    Copyright (C) 2018 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 "signaller.h"
+#include <boost/signals2.hpp>
+
+class MonitorChecker : public Signaller, public boost::noncopyable
+{
+public:
+       ~MonitorChecker ();
+
+       void run ();
+
+       bool ok () const;
+       boost::signals2::signal<void (void)> StateChanged;
+
+       static MonitorChecker* instance ();
+private:
+       static MonitorChecker* _instance;
+
+       MonitorChecker ();
+       void thread ();
+
+       boost::thread* _thread;
+       mutable boost::mutex _mutex;
+       bool _terminate;
+       bool _ok;
+};
index 609ddb3..5e5a868 100644 (file)
@@ -70,6 +70,7 @@ sources = """
           digester.cc
           dkdm_wrapper.cc
           dolby_cp750.cc
+          edid.cc
           emailer.cc
           empty.cc
           encoder.cc
@@ -115,6 +116,7 @@ sources = """
           log_entry.cc
           magick_image_proxy.cc
           mid_side_decoder.cc
+          monitor_checker.cc
           overlaps.cc
           player.cc
           player_text.cc
index 1742f65..5bc9ace 100644 (file)
@@ -47,6 +47,7 @@
 #include "lib/server.h"
 #include "lib/dcpomatic_socket.h"
 #include "lib/scoped_temporary.h"
+#include "lib/monitor_checker.h"
 #include <dcp/dcp.h>
 #include <wx/wx.h>
 #include <wx/stdpaths.h>
@@ -195,10 +196,21 @@ public:
 
                UpdateChecker::instance()->StateChanged.connect (boost::bind (&DOMFrame::update_checker_state_changed, this));
                _controls->SPLChanged.connect (boost::bind(&DOMFrame::set_spl, this, _1));
-
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+               MonitorChecker::instance()->StateChanged.connect(boost::bind(&DOMFrame::monitor_checker_state_changed, this));
+               MonitorChecker::instance()->run ();
+#endif
                setup_screen ();
        }
 
+       void monitor_checker_state_changed ()
+       {
+               if (!MonitorChecker::instance()->ok()) {
+                       error_dialog (this, _("The required display devices are not connected correctly."));
+                       _viewer->stop ();
+               }
+       }
+
        void setup_main_sizer (Config::PlayerMode mode)
        {
                wxSizer* main_sizer = new wxBoxSizer (wxVERTICAL);
@@ -213,6 +225,12 @@ public:
 
        bool playback_permitted ()
        {
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+               if (!MonitorChecker::instance()->ok()) {
+                       error_dialog (this, _("The required display devices are not connected correctly."));
+                       return false;
+               }
+#endif
                if (!_film || !Config::instance()->respect_kdm_validity_periods()) {
                        return true;
                }
index 6815882..eb06f4c 100644 (file)
@@ -41,7 +41,8 @@ public:
                boost::function<void (std::vector<T>)> set,
                boost::function<std::string (T, int)> column,
                bool can_edit = true,
-               bool title = true
+               bool title = true,
+               int column_width = 200
                )
                : wxPanel (parent)
                , _get (get)
@@ -57,13 +58,13 @@ public:
                if (title) {
                        style |= wxLC_NO_HEADER;
                }
-               _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (columns.size() * 200, 100), style);
+               _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (columns.size() * column_width, 100), style);
 
                for (size_t i = 0; i < columns.size(); ++i) {
                        wxListItem ip;
                        ip.SetId (i);
                        ip.SetText (std_to_wx (columns[i]));
-                       ip.SetWidth (200);
+                       ip.SetWidth (column_width);
                        _list->InsertColumn (i, ip);
                }
 
diff --git a/src/wx/monitor_dialog.cc b/src/wx/monitor_dialog.cc
new file mode 100644 (file)
index 0000000..d247bc8
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+    Copyright (C) 2018 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 "monitor_dialog.h"
+#include "wx_util.h"
+#include "lib/encode_server.h"
+#include <dcp/locale_convert.h>
+
+using std::string;
+using dcp::locale_convert;
+using boost::shared_ptr;
+using boost::optional;
+
+MonitorDialog::MonitorDialog (wxWindow* parent)
+       : TableDialog (parent, _("Device"), 2, 1, true)
+{
+       add (_("Manufacturer ID"), true);
+       _manufacturer_id = add (new wxTextCtrl(this, wxID_ANY, wxT("")));
+       add (_("Manufacturer product code"), true);
+       _manufacturer_product_code = add (new wxTextCtrl(this, wxID_ANY, wxT("")));
+       add (_("Serial number"), true);
+       _serial_number = add (new wxTextCtrl(this, wxID_ANY, wxT("")));
+       add (_("Week of manufacture"), true);
+       _week_of_manufacture = add (new wxTextCtrl(this, wxID_ANY, wxT("")));
+       add (_("Year of manufacture"), true);
+       _year_of_manufacture = add (new wxTextCtrl(this, wxID_ANY, wxT("")));
+
+       layout ();
+}
+
+void
+MonitorDialog::set (Monitor monitor)
+{
+       _manufacturer_id->SetValue (std_to_wx(monitor.manufacturer_id));
+       _manufacturer_product_code->SetValue (std_to_wx(locale_convert<string>(monitor.manufacturer_product_code)));
+       _serial_number->SetValue (std_to_wx(locale_convert<string>(monitor.serial_number)));
+       _week_of_manufacture->SetValue (std_to_wx(locale_convert<string>(monitor.week_of_manufacture)));
+       _year_of_manufacture->SetValue (std_to_wx(locale_convert<string>(monitor.year_of_manufacture)));
+}
+
+optional<Monitor>
+MonitorDialog::get () const
+{
+       Monitor m;
+       m.manufacturer_id = wx_to_std (_manufacturer_id->GetValue());
+       m.manufacturer_product_code = locale_convert<uint16_t>(wx_to_std(_manufacturer_product_code->GetValue()));
+       m.serial_number = locale_convert<uint32_t>(wx_to_std(_serial_number->GetValue()));
+       m.week_of_manufacture = locale_convert<uint8_t>(wx_to_std(_week_of_manufacture->GetValue()));
+       m.year_of_manufacture = locale_convert<uint8_t>(wx_to_std (_year_of_manufacture->GetValue()));
+       return m;
+}
diff --git a/src/wx/monitor_dialog.h b/src/wx/monitor_dialog.h
new file mode 100644 (file)
index 0000000..00804f4
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2012 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 "table_dialog.h"
+#include "lib/edid.h"
+#include <boost/optional.hpp>
+
+class MonitorDialog : public TableDialog
+{
+public:
+       explicit MonitorDialog (wxWindow *);
+
+       void set (Monitor);
+       boost::optional<Monitor> get () const;
+
+private:
+       wxTextCtrl* _manufacturer_id;
+       wxTextCtrl* _manufacturer_product_code;
+       wxTextCtrl* _serial_number;
+       wxTextCtrl* _week_of_manufacture;
+       wxTextCtrl* _year_of_manufacture;
+};
index c588c90..29b30f8 100644 (file)
@@ -34,7 +34,7 @@
 #include "email_dialog.h"
 #include "name_format_editor.h"
 #include "nag_dialog.h"
-#include "config_dialog.h"
+#include "monitor_dialog.h"
 #include "lib/config.h"
 #include "lib/ratio.h"
 #include "lib/filter.h"
@@ -318,6 +318,80 @@ private:
        wxSpinCtrl* _period;
        wxSpinCtrl* _duration;
 };
+
+class DevicesPage : public StandardPage
+{
+public:
+       DevicesPage (wxSize panel_size, int border)
+               : StandardPage (panel_size, border)
+       {}
+
+       wxString GetName () const
+       {
+               return _("Devices");
+       }
+
+private:
+       void setup ()
+       {
+               vector<string> columns;
+               columns.push_back(wx_to_std(_("Manufacturer ID")));
+               columns.push_back(wx_to_std(_("Product code")));
+               columns.push_back(wx_to_std(_("Serial")));
+               columns.push_back(wx_to_std(_("Manufacture week")));
+               columns.push_back(wx_to_std(_("Manufacture year")));
+               _monitor_list = new EditableList<Monitor, MonitorDialog> (
+                       _panel,
+                       columns,
+                       boost::bind (&Config::required_monitors, Config::instance()),
+                       boost::bind (&Config::set_required_monitors, Config::instance(), _1),
+                       boost::bind (&DevicesPage::monitor_column, this, _1, _2),
+                       true,
+                       true,
+                       100
+                       );
+               _panel->GetSizer()->Add(_monitor_list, 1, wxEXPAND | wxALL, _border);
+
+               wxButton* get = new wxButton(_panel, wxID_ANY, _("Read current devices"));
+               _panel->GetSizer()->Add(get, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
+               get->Bind(wxEVT_BUTTON, bind(&DevicesPage::get_clicked, this));
+       }
+
+       void get_clicked ()
+       {
+               Config::instance()->set_required_monitors(get_monitors());
+               _monitor_list->refresh ();
+       }
+
+       string monitor_column (Monitor m, int c)
+       {
+               switch (c) {
+               case 0:
+                       return m.manufacturer_id;
+               case 1:
+                       return locale_convert<string>(m.manufacturer_product_code);
+               case 2:
+                       return locale_convert<string>(m.serial_number);
+               case 3:
+                       return locale_convert<string>(m.week_of_manufacture);
+               case 4:
+                       return locale_convert<string>(m.year_of_manufacture);
+               default:
+                       DCPOMATIC_ASSERT(false);
+               }
+
+               return "";
+       }
+
+       void config_changed ()
+       {
+               _monitor_list->refresh ();
+       }
+
+private:
+       EditableList<Monitor, MonitorDialog>* _monitor_list;
+};
+
 #endif
 
 wxPreferencesEditor*
@@ -337,10 +411,11 @@ create_player_config_dialog ()
        int const border = 8;
 #endif
 
-       e->AddPage (new PlayerGeneralPage (ps, border));
-       e->AddPage (new KeysPage (ps, border));
+       e->AddPage (new PlayerGeneralPage(ps, border));
+       e->AddPage (new KeysPage(ps, border));
 #ifdef DCPOMATIC_VARIANT_SWAROOP
-       e->AddPage (new WatermarkPage (ps, border));
+       e->AddPage (new WatermarkPage(ps, border));
+       e->AddPage (new DevicesPage(ps, border));
 #endif
        return e;
 }
index ad0292a..5207608 100644 (file)
@@ -84,6 +84,7 @@ sources = """
           key_dialog.cc
           make_chain_dialog.cc
           message_dialog.cc
+          monitor_dialog.cc
           move_to_dialog.cc
           nag_dialog.cc
           name_format_editor.cc