28#include <wx/checkbox.h>
29#include <wx/dcclient.h>
34#include <wx/statline.h>
35#include <wx/stattext.h>
36#include <wx/textctrl.h>
38#include "udev_rule_mgr.h"
39#include "linux_devices.h"
42#include "ocpn_utils.h"
44static bool hide_dongle_dialog;
45static bool hide_device_dialog;
47static const char*
const DONGLE_INTRO = _(R
"""(
48An OpenCPN dongle is detected but cannot be used due to missing permissions.
50This problem can be fixed by installing a udev rules file. Once installed,
51it will ensure that the dongle permissions are OK.
54static const char*
const FLATPAK_INTRO_TRAILER = _(R
"""(
56On flatpak, this must be done using the manual command instructions below
59static const char*
const DEVICE_INTRO = _(R
"""(
60The device @DEVICE@ exists but cannot be used due to missing permissions.
62This problem can be fixed by installing a udev rules file. Once installed,
63the rules file will fix the permissions problem.
65It will also create a new device called @SYMLINK@. It is recommended to use
66@SYMLINK@ instead of @DEVICE@ to avoid problems with changing device names,
67in particular on laptops.
70static const char*
const HIDE_DIALOG_LABEL =
71 _(
"Do not show this dialog next time");
73static const char*
const RULE_SUCCESS_MSG = _(R
"""(
74Rule successfully installed. To activate the new rule:
76- Unplug and re-insert the USB device.
80static const char*
const FLATPAK_INSTALL_MSG = _(R
"""(
81To do after installing the rule according to instructions:
83- Unplug and re-insert the USB device.
87static const char*
const DEVICE_NOT_FOUND =
88 _(
"The device @device@ can not be found (disconnected?)");
92 HideCheckbox(wxWindow* parent,
const char* label,
bool* state)
93 : wxCheckBox(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
98 [&](wxCommandEvent& ev) { *m_state = ev.IsChecked(); });
107 HidePanel(wxWindow* parent,
const char* label,
bool* state)
109 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
110 hbox->Add(1, 1, 100, wxEXPAND);
111 hbox->Add(
new HideCheckbox(
this, label, state), wxSizerFlags().Expand());
118static const char*
const INSTRUCTIONS =
"@pkexec@ cp @PATH@ /etc/udev/rules.d";
124 : wxPanel(parent), m_show(
true), m_child(child) {
125 m_arrow =
new wxStaticText(
this, wxID_ANY,
"");
126 m_arrow->Bind(wxEVT_LEFT_DOWN, [&](wxMouseEvent& ev) { toggle(); });
135 wxStaticText* m_arrow;
138 static const auto ARROW_DOWN = L
"\u25BC";
139 static const auto ARROW_RIGHT = L
"\u25BA";
142 m_child->Show(m_show);
143 m_arrow->SetLabel(m_show ? ARROW_DOWN : ARROW_RIGHT);
144 GetGrandParent()->Fit();
145 GetGrandParent()->Layout();
154 m_child = get_cmd(parent, cmd);
156 auto flags = wxSizerFlags().Expand().Border().Right();
158 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
159 const char* label = _(
"Manual command line instructions");
160 hbox->Add(
new wxStaticText(
this, wxID_ANY, label), flags);
163 auto vbox =
new wxBoxSizer(wxVERTICAL);
165 auto indent = parent->GetTextExtent(
"aaa").GetWidth();
166 flags = flags.Border(wxLEFT, indent);
167 vbox->Add(m_child, flags.ReserveSpaceEvenIfHidden());
175 wxTextCtrl* get_cmd(wxWindow* parent,
const char* tmpl) {
176 std::string cmd(tmpl);
177 ocpn::replace(cmd,
"@PATH@", get_dongle_rule());
178 auto ctrl =
new wxTextCtrl(
this, wxID_ANY, cmd);
179 ctrl->SetEditable(
false);
180 ctrl->SetMinSize(parent->GetTextExtent(cmd +
"aaa"));
189 ReviewRule(wxWindow* parent,
const std::string& rule)
191 int from = rule[0] ==
'\n' ? 1 : 0;
192 m_child =
new wxStaticText(
this, wxID_ANY, rule.substr(from));
195 auto flags = wxSizerFlags().Expand().Border().Right();
196 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
197 hbox->Add(
new wxStaticText(
this, wxID_ANY, _(
"Review rule")), flags);
200 auto vbox =
new wxBoxSizer(wxVERTICAL);
202 auto indent = parent->GetTextExtent(
"ABCDE").GetWidth();
203 flags = flags.Border(wxLEFT, indent);
204 vbox->Add(m_child, flags.ReserveSpaceEvenIfHidden());
212static std::string get_rule(
const std::string& path) {
213 std::ifstream input(path.c_str());
214 std::ostringstream buf;
215 buf << input.rdbuf();
218 WARNING_LOG <<
"Cannot open rule file: " << path;
227 std::string cmd(INSTRUCTIONS);
228 std::string rule_path(get_dongle_rule());
229 ocpn::replace(cmd,
"@PATH@", rule_path.c_str());
230 ocpn::replace(cmd,
"@pkexec@",
"sudo");
231 auto vbox =
new wxBoxSizer(wxVERTICAL);
233 std::string rule_text = get_rule(rule_path);
234 vbox->Add(
new ReviewRule(
this, rule_text.c_str()));
245 std::string cmd(INSTRUCTIONS);
246 ocpn::replace(cmd,
"@PATH@", rule_path.c_str());
247 ocpn::replace(cmd,
"@pkexec@",
"sudo");
248 auto vbox =
new wxBoxSizer(wxVERTICAL);
250 vbox->Add(
new ReviewRule(
this, get_rule(rule_path)));
258 Buttons(wxWindow* parent,
const char* rule_path)
259 : wxPanel(parent), m_rule_path(rule_path) {
260 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
261 auto flags = wxSizerFlags().Right().Bottom().Border();
262 sizer->Add(1, 1, 100, wxEXPAND);
263 auto install =
new wxButton(
this, wxID_ANY, _(
"Install rule"));
264 install->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
265 [&](wxCommandEvent& ev) { do_install(); });
266 install->Enable(getenv(
"FLATPAK_ID") == NULL);
267 sizer->Add(install, flags);
268 auto quit =
new wxButton(
this, wxID_EXIT, _(
"Quit"));
269 quit->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent& ev) {
270 if (getenv(
"FLATPAK_ID")) {
271 auto flags = wxOK | wxICON_INFORMATION;
272 auto msg = FLATPAK_INSTALL_MSG;
273 OCPNMessageBox(
this, msg, _(
"OpenCPN"), flags);
275 dynamic_cast<wxDialog*
>(GetParent())->EndModal(0);
277 sizer->Add(quit, flags);
284 std::string cmd(INSTRUCTIONS);
285 ocpn::replace(cmd,
"@PATH@", m_rule_path);
286 ocpn::replace(cmd,
"@pkexec@",
"pkexec");
287 int sts = system(cmd.c_str());
288 int flags = wxOK | wxICON_WARNING;
289 const char* msg = _(
"Errors encountered installing rule.");
290 if (WIFEXITED(sts) && WEXITSTATUS(sts) == 0) {
291 msg = RULE_SUCCESS_MSG;
292 flags = wxOK | wxICON_INFORMATION;
294 OCPNMessageBox(
this, msg, _(
"OpenCPN Info"), flags);
297 std::string m_rule_path;
304 : wxDialog(parent, wxID_ANY, _(
"Manage dongle udev rule")) {
305 auto sizer =
new wxBoxSizer(wxVERTICAL);
306 auto flags = wxSizerFlags().Expand().Border();
307 std::string intro(DONGLE_INTRO);
308 if (getenv(
"FLATPAK_ID")) {
309 intro += FLATPAK_INTRO_TRAILER;
311 sizer->Add(
new wxStaticText(
this, wxID_ANY, intro), flags);
312 sizer->Add(
new wxStaticLine(
this), flags);
314 sizer->Add(
new HidePanel(
this, HIDE_DIALOG_LABEL, &hide_dongle_dialog),
316 sizer->Add(
new wxStaticLine(
this), flags);
317 sizer->Add(
new Buttons(
this, get_dongle_rule().c_str()), flags);
325static std::string get_device_intro(
const char* device, std::string
symlink) {
326 std::string intro(DEVICE_INTRO);
327 ocpn::replace(
symlink,
"/dev/",
"");
328 while (intro.find(
"@SYMLINK@") != std::string::npos) {
329 ocpn::replace(intro,
"@SYMLINK@",
symlink);
331 std::string dev_name(device);
332 ocpn::replace(dev_name,
"/dev/",
"");
333 while (intro.find(
"@DEVICE@") != std::string::npos) {
334 ocpn::replace(intro,
"@DEVICE@", dev_name.c_str());
336 if (getenv(
"FLATPAK_ID")) {
337 intro += FLATPAK_INTRO_TRAILER;
346 : wxDialog(parent, wxID_ANY, _(
"Manage device udev rule")) {
347 auto sizer =
new wxBoxSizer(wxVERTICAL);
348 auto flags = wxSizerFlags().Expand().Border();
350 std::string
symlink(make_udev_link());
351 auto intro = get_device_intro(device_path,
symlink.c_str());
352 auto rule_path = get_device_rule(device_path,
symlink.c_str());
353 sizer->Add(
new wxStaticText(
this, wxID_ANY, intro), flags);
354 sizer->Add(
new wxStaticLine(
this), flags);
356 sizer->Add(
new HidePanel(
this, HIDE_DIALOG_LABEL, &hide_device_dialog),
358 sizer->Add(
new wxStaticLine(
this), flags);
359 sizer->Add(
new Buttons(
this, rule_path.c_str()), flags);
367bool CheckSerialAccess(wxWindow* parent,
const std::string device) {
368 if (hide_device_dialog) {
371 if (!ocpn::exists(device)) {
372 std::string msg(DEVICE_NOT_FOUND);
373 ocpn::replace(msg,
"@device@", device);
374 OCPNMessageBox(parent, msg, _(
"OpenCPN device error"));
378 if (!is_device_permissions_ok(device.c_str())) {
380 result = dialog->ShowModal();
386bool CheckDongleAccess(wxWindow* parent) {
388 if (is_dongle_permissions_wrong() && !hide_dongle_dialog) {
390 result = dialog->ShowModal();
The device "manual instructions" and "Review rule" stuff.
Main, top-level device udev rule dialog.
The dongle "manual instructions" and "Review rule" stuff.
Main, top-level Dongle udev rule dialog.
A clickable triangle which controls child window hide/show.
Manual instructions dynamic display.
Review rule dynamic display.
The "Dont show this message next time" checkbox.
Line with "Don't show this message..." checkbox