36#include <wx/checkbox.h>
37#include <wx/dcclient.h>
42#include <wx/statline.h>
43#include <wx/stattext.h>
44#include <wx/textctrl.h>
48#include "model/ocpn_utils.h"
53#if !defined(__linux__) || defined(__ANDROID__)
64static bool hide_dongle_dialog;
65static bool hide_device_dialog;
67static const char*
const DONGLE_INTRO = _(R
"(
68An OpenCPN dongle is detected but cannot be used due to missing permissions.
70This problem can be fixed by installing a udev rules file. Once installed,
71it will ensure that the dongle permissions are OK.
74static const char*
const FLATPAK_INTRO_TRAILER = _(R
"(
76On flatpak, this must be done using the manual command instructions below
79static const char*
const DEVICE_INTRO = _(R
"(
80The device @DEVICE@ exists but cannot be used due to missing permissions.
82This problem can be fixed by installing a udev rules file. Once installed,
83the rules file will fix the permissions problem.
86static const char*
const DEVICE_LINK_INTRO = _(R
"(
88It will also create a new device called @SYMLINK@. It is recommended to use
89@SYMLINK@ instead of @DEVICE@ to avoid problems with changing device names,
90in particular on laptops.
93static const char*
const HIDE_DIALOG_LABEL =
94 _(
"Do not show this dialog next time");
96static const char*
const RULE_SUCCESS_TTYS_MSG = _(R
"(
97Rule successfully installed. To activate the new rule restart the system.
100static const char*
const RULE_SUCCESS_MSG = _(R
"(
101Rule successfully installed. To activate the new rule restart system or:
103- Unplug and re-insert the USB device.
107static const char*
const FLATPAK_INSTALL_MSG = _(R
"(
108To do after installing the rule according to instructions:
110- Unplug and re-insert the USB device.
114static const char*
const DEVICE_NOT_FOUND =
115 _(
"The device @device@ can not be found (disconnected?)");
117static const char*
const INSTRUCTIONS =
"@pkexec@ cp @PATH@ /etc/udev/rules.d";
120class DeviceNotFoundDlg :
public wxFrame {
123 static void Create(wxWindow* parent,
const std::string& device) {
124 wxWindow* dlg =
new DeviceNotFoundDlg(parent, device);
129 static void DestroyOpenWindows() {
130 for (
const auto& name : open_windows) {
131 auto window = wxWindow::FindWindowByName(name);
132 if (window) window->Destroy();
134 open_windows.clear();
138 static std::vector<std::string> open_windows;
142 ButtonsSizer(DeviceNotFoundDlg* parent) : wxStdDialogButtonSizer() {
143 auto button =
new wxButton(parent, wxID_OK);
149 DeviceNotFoundDlg(wxWindow* parent,
const std::string& device)
150 : wxFrame(parent, wxID_ANY, _(
"Opencpn: device not found"),
151 wxDefaultPosition, wxDefaultSize,
152 wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT) {
153 std::stringstream ss;
154 ss <<
"dlg-id-" << rand();
156 open_windows.push_back(ss.str());
158 Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent& e) {
162 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) { OnClose(); });
164 auto vbox =
new wxBoxSizer(wxVERTICAL);
166 auto flags = wxSizerFlags().Expand().Border();
167 std::string txt(DEVICE_NOT_FOUND);
168 ocpn::replace(txt,
"@device@", device);
170 vbox->Add(
new wxStaticText(
this, wxID_ANY, txt), flags);
172 vbox->Add(
new wxStaticLine(
this), wxSizerFlags().Expand());
180 const std::string name(GetName().ToStdString());
182 std::find_if(open_windows.begin(), open_windows.end(),
183 [name](
const std::string& s) { return s == name; });
184 assert(found != std::end(open_windows) &&
185 "Cannot find dialog in window list");
186 open_windows.erase(found);
191std::vector<std::string> DeviceNotFoundDlg::open_windows;
196class HideCheckbox :
public wxCheckBox {
198 HideCheckbox(wxWindow* parent,
const char* label,
bool* state)
199 : wxCheckBox(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
204 [&](wxCommandEvent& ev) { *m_state = ev.IsChecked(); });
212class HidePanel :
public wxPanel {
214 HidePanel(wxWindow* parent,
const char* label,
bool* state)
216 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
217 hbox->Add(
new HideCheckbox(
this, label, state), wxSizerFlags().Expand());
225class HideShowPanel :
public wxPanel {
227 HideShowPanel(wxWindow* parent, wxWindow* child)
228 : wxPanel(parent), m_show(true), m_child(child) {
229 m_arrow =
new wxStaticText(
this, wxID_ANY,
"");
230 m_arrow->Bind(wxEVT_LEFT_DOWN, [&](wxMouseEvent& ev) { Toggle(); });
239 wxStaticText* m_arrow;
242 static const auto ARROW_DOWN = L
"\u25BC";
243 static const auto ARROW_RIGHT = L
"\u25BA";
246 m_child->Show(m_show);
247 m_arrow->SetLabel(std::string(
" ") + (m_show ? ARROW_DOWN : ARROW_RIGHT));
248 GetGrandParent()->Fit();
249 GetGrandParent()->Layout();
254class ManualInstructions :
public HideShowPanel {
256 ManualInstructions(wxWindow* parent,
const char* cmd)
257 : HideShowPanel(parent, 0) {
258 m_child = GetCmd(parent, cmd);
260 auto flags = wxSizerFlags().Expand();
262 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
263 const char* label = _(
"Manual command line instructions");
264 hbox->Add(
new wxStaticText(
this, wxID_ANY, label), flags);
267 auto vbox =
new wxBoxSizer(wxVERTICAL);
270 flags = flags.Border(wxLEFT);
271 vbox->Add(m_child, flags.ReserveSpaceEvenIfHidden());
279 wxTextCtrl* GetCmd(wxWindow* parent,
const char* tmpl) {
280 std::string cmd(tmpl);
283 ctrl->SetMinSize(parent->GetTextExtent(cmd +
"aaa"));
290class ReviewRule :
public HideShowPanel {
292 ReviewRule(wxWindow* parent,
const std::string& rule)
293 : HideShowPanel(parent, 0) {
294 int from = rule[0] ==
'\n' ? 1 : 0;
295 m_child =
new wxStaticText(
this, wxID_ANY, rule.substr(from));
298 auto flags = wxSizerFlags().Expand();
299 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
300 hbox->Add(
new wxStaticText(
this, wxID_ANY, _(
"Review rule")), flags);
303 auto vbox =
new wxBoxSizer(wxVERTICAL);
305 auto indent = parent->GetTextExtent(
"ABCDE").GetWidth();
306 flags = flags.Border(wxLEFT, indent);
307 vbox->Add(m_child, flags.ReserveSpaceEvenIfHidden());
315static std::string GetRule(
const std::string& path) {
316 std::ifstream input(path.c_str());
317 std::ostringstream buf;
318 buf << input.rdbuf();
321 WARNING_LOG <<
"Cannot open rule file: " << path;
327class DongleInfoPanel :
public wxPanel {
329 DongleInfoPanel(wxWindow* parent) : wxPanel(parent) {
330 std::string cmd(INSTRUCTIONS);
332 ocpn::replace(cmd,
"@PATH@", rule_path.c_str());
333 ocpn::replace(cmd,
"@pkexec@",
"sudo");
334 auto vbox =
new wxBoxSizer(wxVERTICAL);
335 vbox->Add(
new ManualInstructions(
this, cmd.c_str()));
336 std::string rule_text = GetRule(rule_path);
337 vbox->Add(
new ReviewRule(
this, rule_text.c_str()));
344class DeviceInfoPanel :
public wxPanel {
346 DeviceInfoPanel(wxWindow* parent,
const std::string rule_path)
348 std::string cmd(INSTRUCTIONS);
349 ocpn::replace(cmd,
"@PATH@", rule_path.c_str());
350 ocpn::replace(cmd,
"@pkexec@",
"sudo");
351 auto vbox =
new wxBoxSizer(wxVERTICAL);
352 vbox->Add(
new ManualInstructions(
this, cmd.c_str()));
353 vbox->Add(
new ReviewRule(
this, GetRule(rule_path)));
360class Buttons :
public wxPanel {
362 Buttons(wxWindow* parent,
const char* rule_path)
363 : wxPanel(parent), m_rule_path(rule_path) {
364 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
365 auto flags = wxSizerFlags().Bottom().Border(wxLEFT);
366 sizer->Add(1, 1, 100, wxEXPAND);
367 auto install =
new wxButton(
this, wxID_ANY, _(
"Install rule"));
368 install->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
369 [&](wxCommandEvent& ev) { DoInstall(); });
370 install->Enable(getenv(
"FLATPAK_ID") == NULL);
371 sizer->Add(install, flags);
372 auto quit =
new wxButton(
this, wxID_EXIT, _(
"Quit"));
373 quit->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent& ev) {
374 if (getenv(
"FLATPAK_ID")) {
375 auto flags = wxOK | wxICON_INFORMATION;
376 auto msg = FLATPAK_INSTALL_MSG;
377 OCPNMessageBox(
this, msg, _(
"OpenCPN"), flags);
379 dynamic_cast<wxDialog*
>(GetParent())->EndModal(0);
381 sizer->Add(quit, flags);
389 string cmd(INSTRUCTIONS);
390 ocpn::replace(cmd,
"@PATH@", m_rule_path);
391 ocpn::replace(cmd,
"@pkexec@",
"sudo");
392 ifstream f(m_rule_path);
394 string(istreambuf_iterator<char>(f), istreambuf_iterator<char>());
395 int sts = system(cmd.c_str());
396 int flags = wxOK | wxICON_WARNING;
397 const char* msg = _(
"Errors encountered installing rule.");
398 if (WIFEXITED(sts) && WEXITSTATUS(sts) == 0) {
399 if (rule.find(
"ttyS") != std::string::npos) {
400 msg = RULE_SUCCESS_TTYS_MSG;
402 msg = RULE_SUCCESS_MSG;
404 flags = wxOK | wxICON_INFORMATION;
406 OCPNMessageBox(
this, msg, _(
"OpenCPN Info"), flags);
410 std::string m_rule_path;
414class DongleRuleDialog :
public wxDialog {
416 DongleRuleDialog(wxWindow* parent)
417 : wxDialog(parent, wxID_ANY, _(
"Manage dongle udev rule")) {
418 auto sizer =
new wxBoxSizer(wxVERTICAL);
419 auto flags = wxSizerFlags().Expand().Border();
420 std::string intro(DONGLE_INTRO);
421 if (getenv(
"FLATPAK_ID")) {
422 intro += FLATPAK_INTRO_TRAILER;
424 sizer->Add(
new wxStaticText(
this, wxID_ANY, intro), flags);
425 sizer->Add(
new wxStaticLine(
this), flags);
426 sizer->Add(
new DongleInfoPanel(
this), flags);
427 sizer->Add(
new HidePanel(
this, HIDE_DIALOG_LABEL, &hide_dongle_dialog),
429 sizer->Add(
new wxStaticLine(
this), flags);
430 sizer->Add(
new Buttons(
this,
GetDongleRule().c_str()), flags);
438static std::string GetDeviceIntro(
const char* device, std::string
symlink) {
439 std::string intro(DEVICE_INTRO);
441 std::string dev_name(device);
442 ocpn::replace(dev_name,
"/dev/",
"");
443 if (!ocpn::startswith(dev_name,
"ttyS")) {
444 intro += DEVICE_LINK_INTRO;
446 if (getenv(
"FLATPAK_ID")) {
447 intro += FLATPAK_INTRO_TRAILER;
449 ocpn::replace(
symlink,
"/dev/",
"");
450 while (intro.find(
"@SYMLINK@") != std::string::npos) {
451 ocpn::replace(intro,
"@SYMLINK@",
symlink);
453 while (intro.find(
"@DEVICE@") != std::string::npos) {
454 ocpn::replace(intro,
"@DEVICE@", dev_name.c_str());
460class DeviceRuleDialog :
public wxDialog {
462 DeviceRuleDialog(wxWindow* parent,
const char* device_path)
463 : wxDialog(parent, wxID_ANY, _(
"Manage device udev rule")) {
464 auto sizer =
new wxBoxSizer(wxVERTICAL);
465 auto flags = wxSizerFlags().Expand().Border();
468 auto intro = GetDeviceIntro(device_path,
symlink.c_str());
470 sizer->Add(
new wxStaticText(
this, wxID_ANY, intro), flags);
471 sizer->Add(
new wxStaticLine(
this), flags);
472 sizer->Add(
new DeviceInfoPanel(
this, rule_path), flags);
473 sizer->Add(
new HidePanel(
this, HIDE_DIALOG_LABEL, &hide_device_dialog),
475 sizer->Add(
new wxStaticLine(
this), flags);
476 sizer->Add(
new Buttons(
this, rule_path.c_str()), flags);
485 if (hide_device_dialog) {
488 if (!ocpn::exists(device)) {
489 DeviceNotFoundDlg::Create(parent, device);
494 auto dialog =
new DeviceRuleDialog(parent, device.c_str());
495 result = dialog->ShowModal();
504 auto dialog =
new DongleRuleDialog(parent);
505 result = dialog->ShowModal();
Non-editable TextCtrl, used like wxStaticText but is copyable.
General purpose GUI support.
std::string MakeUdevLink()
Get next available udev rule base name.
bool IsDevicePermissionsOk(const char *path)
Check device path permissions.
std::string GetDongleRule()
std::string GetDeviceRule(const char *device, const char *symlink)
Get device udev rule.
bool IsDonglePermissionsWrong()
Return true if an existing dongle cannot be accessed.
Low level udev usb device management.
Enhanced logging interface on top of wx/log.h.
void DestroyDeviceNotFoundDialogs()
Destroy all open "Device not found" dialog windows.
bool CheckDongleAccess(wxWindow *parent)
Runs checks and if required dialogs to make dongle accessible.
bool CheckSerialAccess(wxWindow *parent, const std::string device)
Run checks and possible dialogs to ensure device is accessible.
Access checks for comm devices and dongle.