31 #include <wx/button.h>
32 #include <wx/checkbox.h>
33 #include <wx/dcclient.h>
34 #include <wx/dialog.h>
38 #include <wx/statline.h>
39 #include <wx/stattext.h>
40 #include <wx/textctrl.h>
43 #include "model/logger.h"
44 #include "model/ocpn_utils.h"
50 #if !defined(__linux__) || defined(__ANDROID__)
62 static bool hide_dongle_dialog;
63 static bool hide_device_dialog;
65 static const char*
const DONGLE_INTRO = _(R
"(
66 An OpenCPN dongle is detected but cannot be used due to missing permissions.
68 This problem can be fixed by installing a udev rules file. Once installed,
69 it will ensure that the dongle permissions are OK.
72 static const char*
const FLATPAK_INTRO_TRAILER = _(R
"(
74 On flatpak, this must be done using the manual command instructions below
77 static const char*
const DEVICE_INTRO = _(R
"(
78 The device @DEVICE@ exists but cannot be used due to missing permissions.
80 This problem can be fixed by installing a udev rules file. Once installed,
81 the rules file will fix the permissions problem.
84 static const char*
const DEVICE_LINK_INTRO = _(R
"(
86 It will also create a new device called @SYMLINK@. It is recommended to use
87 @SYMLINK@ instead of @DEVICE@ to avoid problems with changing device names,
88 in particular on laptops.
91 static const char*
const HIDE_DIALOG_LABEL =
92 _(
"Do not show this dialog next time");
94 static const char*
const RULE_SUCCESS_TTYS_MSG = _(R
"(
95 Rule successfully installed. To activate the new rule restart the system.
98 static const char*
const RULE_SUCCESS_MSG = _(R
"(
99 Rule successfully installed. To activate the new rule restart system or:
101 - Unplug and re-insert the USB device.
105 static const char*
const FLATPAK_INSTALL_MSG = _(R
"(
106 To do after installing the rule according to instructions:
108 - Unplug and re-insert the USB device.
112 static const char*
const DEVICE_NOT_FOUND =
113 _(
"The device @device@ can not be found (disconnected?)");
115 static const char*
const INSTRUCTIONS =
"@pkexec@ cp @PATH@ /etc/udev/rules.d";
118 class DeviceNotFoundDlg :
public wxFrame {
121 static void Create(wxWindow* parent,
const std::string& device) {
122 wxWindow* dlg =
new DeviceNotFoundDlg(parent, device);
127 static void DestroyOpenWindows() {
128 for (
const auto& name : open_windows) {
129 auto window = wxWindow::FindWindowByName(name);
130 if (window) window->Destroy();
132 open_windows.clear();
136 static std::vector<std::string> open_windows;
140 ButtonsSizer(DeviceNotFoundDlg* parent) : wxStdDialogButtonSizer() {
141 auto button =
new wxButton(parent, wxID_OK);
147 DeviceNotFoundDlg(wxWindow* parent,
const std::string& device)
148 : wxFrame(parent, wxID_ANY, _(
"Opencpn: device not found"),
149 wxDefaultPosition, wxDefaultSize,
150 wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT) {
151 std::stringstream ss;
152 ss <<
"dlg-id-" << rand();
154 open_windows.push_back(ss.str());
156 Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent& e) {
160 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) { OnClose(); });
162 auto vbox =
new wxBoxSizer(wxVERTICAL);
164 auto flags = wxSizerFlags().Expand().Border();
165 std::string txt(DEVICE_NOT_FOUND);
166 ocpn::replace(txt,
"@device@", device);
168 vbox->Add(
new wxStaticText(
this, wxID_ANY, txt), flags);
170 vbox->Add(
new wxStaticLine(
this), wxSizerFlags().Expand());
178 const std::string name(GetName().ToStdString());
180 std::find_if(open_windows.begin(), open_windows.end(),
181 [name](
const std::string& s) { return s == name; });
182 assert(found != std::end(open_windows) &&
183 "Cannot find dialog in window list");
184 open_windows.erase(found);
189 std::vector<std::string> DeviceNotFoundDlg::open_windows;
194 class HideCheckbox :
public wxCheckBox {
196 HideCheckbox(wxWindow* parent,
const char* label,
bool* state)
197 : wxCheckBox(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
202 [&](wxCommandEvent& ev) { *m_state = ev.IsChecked(); });
210 class HidePanel :
public wxPanel {
212 HidePanel(wxWindow* parent,
const char* label,
bool* state)
214 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
215 hbox->Add(
new HideCheckbox(
this, label, state), wxSizerFlags().Expand());
223 class HideShowPanel :
public wxPanel {
225 HideShowPanel(wxWindow* parent, wxWindow* child)
226 : wxPanel(parent), m_show(true), m_child(child) {
227 m_arrow =
new wxStaticText(
this, wxID_ANY,
"");
228 m_arrow->Bind(wxEVT_LEFT_DOWN, [&](wxMouseEvent& ev) { Toggle(); });
237 wxStaticText* m_arrow;
240 static const auto ARROW_DOWN = L
"\u25BC";
241 static const auto ARROW_RIGHT = L
"\u25BA";
244 m_child->Show(m_show);
245 m_arrow->SetLabel(std::string(
" ") + (m_show ? ARROW_DOWN : ARROW_RIGHT));
246 GetGrandParent()->Fit();
247 GetGrandParent()->Layout();
252 class ManualInstructions :
public HideShowPanel {
254 ManualInstructions(wxWindow* parent,
const char* cmd)
255 : HideShowPanel(parent, 0) {
256 m_child = GetCmd(parent, cmd);
258 auto flags = wxSizerFlags().Expand();
260 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
261 const char* label = _(
"Manual command line instructions");
262 hbox->Add(
new wxStaticText(
this, wxID_ANY, label), flags);
265 auto vbox =
new wxBoxSizer(wxVERTICAL);
268 flags = flags.Border(wxLEFT);
269 vbox->Add(m_child, flags.ReserveSpaceEvenIfHidden());
277 wxTextCtrl* GetCmd(wxWindow* parent,
const char* tmpl) {
278 std::string cmd(tmpl);
281 ctrl->SetMinSize(parent->GetTextExtent(cmd +
"aaa"));
288 class ReviewRule :
public HideShowPanel {
290 ReviewRule(wxWindow* parent,
const std::string& rule)
291 : HideShowPanel(parent, 0) {
292 int from = rule[0] ==
'\n' ? 1 : 0;
293 m_child =
new wxStaticText(
this, wxID_ANY, rule.substr(from));
296 auto flags = wxSizerFlags().Expand();
297 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
298 hbox->Add(
new wxStaticText(
this, wxID_ANY, _(
"Review rule")), flags);
301 auto vbox =
new wxBoxSizer(wxVERTICAL);
303 auto indent = parent->GetTextExtent(
"ABCDE").GetWidth();
304 flags = flags.Border(wxLEFT, indent);
305 vbox->Add(m_child, flags.ReserveSpaceEvenIfHidden());
313 static std::string GetRule(
const std::string& path) {
314 std::ifstream input(path.c_str());
315 std::ostringstream buf;
316 buf << input.rdbuf();
319 WARNING_LOG <<
"Cannot open rule file: " << path;
325 class DongleInfoPanel :
public wxPanel {
327 DongleInfoPanel(wxWindow* parent) : wxPanel(parent) {
328 std::string cmd(INSTRUCTIONS);
330 ocpn::replace(cmd,
"@PATH@", rule_path.c_str());
331 ocpn::replace(cmd,
"@pkexec@",
"sudo");
332 auto vbox =
new wxBoxSizer(wxVERTICAL);
333 vbox->Add(
new ManualInstructions(
this, cmd.c_str()));
334 std::string rule_text = GetRule(rule_path);
335 vbox->Add(
new ReviewRule(
this, rule_text.c_str()));
342 class DeviceInfoPanel :
public wxPanel {
344 DeviceInfoPanel(wxWindow* parent,
const std::string rule_path)
346 std::string cmd(INSTRUCTIONS);
347 ocpn::replace(cmd,
"@PATH@", rule_path.c_str());
348 ocpn::replace(cmd,
"@pkexec@",
"sudo");
349 auto vbox =
new wxBoxSizer(wxVERTICAL);
350 vbox->Add(
new ManualInstructions(
this, cmd.c_str()));
351 vbox->Add(
new ReviewRule(
this, GetRule(rule_path)));
358 class Buttons :
public wxPanel {
360 Buttons(wxWindow* parent,
const char* rule_path)
361 : wxPanel(parent), m_rule_path(rule_path) {
362 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
363 auto flags = wxSizerFlags().Bottom().Border(wxLEFT);
364 sizer->Add(1, 1, 100, wxEXPAND);
365 auto install =
new wxButton(
this, wxID_ANY, _(
"Install rule"));
366 install->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
367 [&](wxCommandEvent& ev) { DoInstall(); });
368 install->Enable(getenv(
"FLATPAK_ID") == NULL);
369 sizer->Add(install, flags);
370 auto quit =
new wxButton(
this, wxID_EXIT, _(
"Quit"));
371 quit->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent& ev) {
372 if (getenv(
"FLATPAK_ID")) {
373 auto flags = wxOK | wxICON_INFORMATION;
374 auto msg = FLATPAK_INSTALL_MSG;
375 OCPNMessageBox(
this, msg, _(
"OpenCPN"), flags);
377 dynamic_cast<wxDialog*
>(GetParent())->EndModal(0);
379 sizer->Add(quit, flags);
387 string cmd(INSTRUCTIONS);
388 ocpn::replace(cmd,
"@PATH@", m_rule_path);
389 ocpn::replace(cmd,
"@pkexec@",
"sudo");
390 ifstream f(m_rule_path);
392 string(istreambuf_iterator<char>(f), istreambuf_iterator<char>());
393 int sts = system(cmd.c_str());
394 int flags = wxOK | wxICON_WARNING;
395 const char* msg = _(
"Errors encountered installing rule.");
396 if (WIFEXITED(sts) && WEXITSTATUS(sts) == 0) {
397 if (rule.find(
"ttyS") != std::string::npos) {
398 msg = RULE_SUCCESS_TTYS_MSG;
400 msg = RULE_SUCCESS_MSG;
402 flags = wxOK | wxICON_INFORMATION;
404 OCPNMessageBox(
this, msg, _(
"OpenCPN Info"), flags);
408 std::string m_rule_path;
412 class DongleRuleDialog :
public wxDialog {
414 DongleRuleDialog(wxWindow* parent)
415 : wxDialog(parent, wxID_ANY, _(
"Manage dongle udev rule")) {
416 auto sizer =
new wxBoxSizer(wxVERTICAL);
417 auto flags = wxSizerFlags().Expand().Border();
418 std::string intro(DONGLE_INTRO);
419 if (getenv(
"FLATPAK_ID")) {
420 intro += FLATPAK_INTRO_TRAILER;
422 sizer->Add(
new wxStaticText(
this, wxID_ANY, intro), flags);
423 sizer->Add(
new wxStaticLine(
this), flags);
424 sizer->Add(
new DongleInfoPanel(
this), flags);
425 sizer->Add(
new HidePanel(
this, HIDE_DIALOG_LABEL, &hide_dongle_dialog),
427 sizer->Add(
new wxStaticLine(
this), flags);
428 sizer->Add(
new Buttons(
this,
GetDongleRule().c_str()), flags);
436 static std::string GetDeviceIntro(
const char* device, std::string
symlink) {
437 std::string intro(DEVICE_INTRO);
439 std::string dev_name(device);
440 ocpn::replace(dev_name,
"/dev/",
"");
441 if (!ocpn::startswith(dev_name,
"ttyS")) {
442 intro += DEVICE_LINK_INTRO;
444 if (getenv(
"FLATPAK_ID")) {
445 intro += FLATPAK_INTRO_TRAILER;
447 ocpn::replace(
symlink,
"/dev/",
"");
448 while (intro.find(
"@SYMLINK@") != std::string::npos) {
449 ocpn::replace(intro,
"@SYMLINK@",
symlink);
451 while (intro.find(
"@DEVICE@") != std::string::npos) {
452 ocpn::replace(intro,
"@DEVICE@", dev_name.c_str());
458 class DeviceRuleDialog :
public wxDialog {
460 DeviceRuleDialog(wxWindow* parent,
const char* device_path)
461 : wxDialog(parent, wxID_ANY, _(
"Manage device udev rule")) {
462 auto sizer =
new wxBoxSizer(wxVERTICAL);
463 auto flags = wxSizerFlags().Expand().Border();
466 auto intro = GetDeviceIntro(device_path,
symlink.c_str());
468 sizer->Add(
new wxStaticText(
this, wxID_ANY, intro), flags);
469 sizer->Add(
new wxStaticLine(
this), flags);
470 sizer->Add(
new DeviceInfoPanel(
this, rule_path), flags);
471 sizer->Add(
new HidePanel(
this, HIDE_DIALOG_LABEL, &hide_device_dialog),
473 sizer->Add(
new wxStaticLine(
this), flags);
474 sizer->Add(
new Buttons(
this, rule_path.c_str()), flags);
483 if (hide_device_dialog) {
486 if (!ocpn::exists(device)) {
487 DeviceNotFoundDlg::Create(parent, device);
492 auto dialog =
new DeviceRuleDialog(parent, device.c_str());
493 result = dialog->ShowModal();
502 auto dialog =
new DongleRuleDialog(parent);
503 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.
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.