OpenCPN Partial API docs
Loading...
Searching...
No Matches
udev_rule_mgr.cpp
Go to the documentation of this file.
1
2/**************************************************************************
3 * Copyright (C) 2021 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
26#include "config.h"
27
28#include <algorithm>
29#include <cassert>
30#include <sstream>
31#include <vector>
32
33#include <stdlib.h>
34
35#include <wx/button.h>
36#include <wx/checkbox.h>
37#include <wx/dcclient.h>
38#include <wx/dialog.h>
39#include <wx/frame.h>
40#include <wx/panel.h>
41#include <wx/sizer.h>
42#include <wx/statline.h>
43#include <wx/stattext.h>
44#include <wx/textctrl.h>
45
46#include "model/linux_devices.h"
47#include "model/logger.h"
49#include "model/ocpn_utils.h"
50
51#include "expand_panel.h"
52#include "gui_lib.h"
53#include "udev_rule_mgr.h"
54
55#if !defined(__linux__) || defined(__ANDROID__)
56
57// non-linux platforms: Empty place holders.
58bool CheckDongleAccess(wxWindow* parent) { return true; }
59bool CheckSerialAccess(wxWindow* parent, const std::string device) {
60 return true;
61}
63
64#else
65
66static bool hide_dongle_dialog;
67static bool hide_device_dialog;
68
69static const char* const DONGLE_INTRO = _(R"(
70An OpenCPN dongle is detected but cannot be used due to missing permissions.
71
72This problem can be fixed by installing a udev rules file. Once installed,
73it will ensure that the dongle permissions are OK.
74)");
75
76static const char* const FLATPAK_INTRO_TRAILER = _(R"(
77
78On flatpak, this must be done using the manual command instructions below
79)");
80
81static const char* const DEVICE_INTRO = _(R"(
82The device @DEVICE@ exists but cannot be used due to missing permissions.
83
84This problem can be fixed by installing a udev rules file. Once installed,
85the rules file will fix the permissions problem.
86)");
87
88static const char* const DEVICE_LINK_INTRO = _(R"(
89
90It will also create a new device called @SYMLINK@. It is recommended to use
91@SYMLINK@ instead of @DEVICE@ to avoid problems with changing device names,
92in particular on laptops.
93)");
94
95static const char* const HIDE_DIALOG_LABEL =
96 _("Do not show this dialog next time");
97
98static const char* const RULE_SUCCESS_TTYS_MSG = _(R"(
99Rule successfully installed. To activate the new rule restart the system.
100)");
101
102static const char* const RULE_SUCCESS_MSG = _(R"(
103Rule successfully installed. To activate the new rule restart system or:
104- Exit opencpn.
105- Unplug and re-insert the USB device.
106- Restart opencpn
107)");
108
109static const char* const FLATPAK_INSTALL_MSG = _(R"(
110To do after installing the rule according to instructions:
111- Exit opencpn.
112- Unplug and re-insert the USB device.
113- Restart opencpn
114)");
115
116static const char* const DEVICE_NOT_FOUND =
117 _("The device @device@ can not be found (disconnected?)");
118
119static const char* const INSTRUCTIONS = "@pkexec@ cp @PATH@ /etc/udev/rules.d";
120
122class DeviceNotFoundDlg : public wxFrame {
123public:
125 static void Create(wxWindow* parent, const std::string& device) {
126 wxWindow* dlg = new DeviceNotFoundDlg(parent, device);
127 dlg->Show();
128 }
129
131 static void DestroyOpenWindows() {
132 for (const auto& name : open_windows) {
133 auto window = wxWindow::FindWindowByName(name);
134 if (window) window->Destroy();
135 }
136 open_windows.clear();
137 }
138
139private:
140 static std::vector<std::string> open_windows;
141
142 class ButtonsSizer : public wxStdDialogButtonSizer {
143 public:
144 ButtonsSizer(DeviceNotFoundDlg* parent) : wxStdDialogButtonSizer() {
145 auto button = new wxButton(parent, wxID_OK);
146 AddButton(button);
147 Realize();
148 }
149 };
150
151 DeviceNotFoundDlg(wxWindow* parent, const std::string& device)
152 : wxFrame(parent, wxID_ANY, _("Opencpn: device not found"),
153 wxDefaultPosition, wxDefaultSize,
154 wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT) {
155 std::stringstream ss;
156 ss << "dlg-id-" << rand();
157 SetName(ss.str());
158 open_windows.push_back(ss.str());
159
160 Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent& e) {
161 OnClose();
162 e.Skip();
163 });
164 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) { OnClose(); });
165
166 auto vbox = new wxBoxSizer(wxVERTICAL);
167 SetSizer(vbox);
168 auto flags = wxSizerFlags().Expand().Border();
169 std::string txt(DEVICE_NOT_FOUND);
170 ocpn::replace(txt, "@device@", device);
171 vbox->Add(0, 0, 1); // vertical space
172 vbox->Add(new wxStaticText(this, wxID_ANY, txt), flags);
173 vbox->Add(0, 0, 1);
174 vbox->Add(new wxStaticLine(this), wxSizerFlags().Expand());
175 vbox->Add(new ButtonsSizer(this), flags);
176 Layout();
177 CenterOnScreen();
178 SetFocus();
179 }
180
181 void OnClose() {
182 const std::string name(GetName().ToStdString());
183 auto found =
184 std::find_if(open_windows.begin(), open_windows.end(),
185 [name](const std::string& s) { return s == name; });
186 assert(found != std::end(open_windows) &&
187 "Cannot find dialog in window list");
188 open_windows.erase(found);
189 Destroy();
190 }
191};
192
193std::vector<std::string> DeviceNotFoundDlg::open_windows;
194
195void DestroyDeviceNotFoundDialogs() { DeviceNotFoundDlg::DestroyOpenWindows(); }
196
198class HideCheckbox : public wxCheckBox {
199public:
200 HideCheckbox(wxWindow* parent, const char* label, bool* state)
201 : wxCheckBox(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
202 wxALIGN_LEFT),
203 m_state(state) {
204 SetValue(*state);
205 Bind(wxEVT_CHECKBOX,
206 [&](wxCommandEvent& ev) { *m_state = ev.IsChecked(); });
207 }
208
209private:
210 bool* m_state;
211};
212
214class HidePanel : public wxPanel {
215public:
216 HidePanel(wxWindow* parent, const char* label, bool* state)
217 : wxPanel(parent) {
218 auto hbox = new wxBoxSizer(wxHORIZONTAL);
219 hbox->Add(new HideCheckbox(this, label, state), wxSizerFlags().Expand());
220 SetSizer(hbox);
221 Fit();
222 Show();
223 }
224};
225
227class ManualInstructions : public ExpandablePanel {
228public:
229 ManualInstructions(wxWindow* parent, const char* cmd)
230 : ExpandablePanel(parent, nullptr) {
231 Create(GetCmd(parent, cmd));
232 auto flags = wxSizerFlags().Expand();
233 auto hbox = new wxBoxSizer(wxHORIZONTAL);
234
235 const char* label = _("Manual command line instructions");
236 hbox->Add(new wxStaticText(this, wxID_ANY, label), flags);
237 hbox->Add(GetIcon());
238
239 auto vbox = new wxBoxSizer(wxVERTICAL);
240 vbox->Add(hbox);
241 flags = flags.Border(wxLEFT);
242 vbox->Add(GetChild(), flags.ReserveSpaceEvenIfHidden());
243
244 SetSizer(vbox);
245 SetAutoLayout(true);
246 Show();
247 }
248
249private:
250 wxTextCtrl* GetCmd(wxWindow* parent, const char* tmpl) {
251 std::string cmd(tmpl);
252 ocpn::replace(cmd, "@PATH@", GetDongleRule());
253 auto ctrl = new CopyableText(this, cmd.c_str());
254 ctrl->SetMinSize(parent->GetTextExtent(cmd + "aaa"));
255 return ctrl;
256 }
257 wxWindow* m_parent;
258};
259
261class ReviewRule : public ExpandablePanel {
262public:
263 ReviewRule(wxWindow* parent, const std::string& rule)
264 : ExpandablePanel(parent) {
265 int from = rule[0] == '\n' ? 1 : 0;
266 Create(new wxStaticText(this, wxID_ANY, rule.substr(from)));
267
268 auto flags = wxSizerFlags().Expand();
269 auto hbox = new wxBoxSizer(wxHORIZONTAL);
270 hbox->Add(new wxStaticText(this, wxID_ANY, _("Review rule")), flags);
271 hbox->Add(GetIcon(), flags);
272 auto vbox = new wxBoxSizer(wxVERTICAL);
273 vbox->Add(hbox);
274 auto indent = parent->GetTextExtent("ABCDE").GetWidth();
275 flags = flags.Border(wxLEFT, indent);
276
277 vbox->Add(GetChild(), flags.ReserveSpaceEvenIfHidden());
278 SetSizer(vbox);
279 SetAutoLayout(true);
280 Show();
281 }
282};
283
285static std::string GetRule(const std::string& path) {
286 std::ifstream input(path.c_str());
287 std::ostringstream buf;
288 buf << input.rdbuf();
289 input.close();
290 if (input.bad()) {
291 WARNING_LOG << "Cannot open rule file: " << path;
292 }
293 return buf.str();
294}
295
297class DongleInfoPanel : public wxPanel {
298public:
299 DongleInfoPanel(wxWindow* parent) : wxPanel(parent) {
300 std::string cmd(INSTRUCTIONS);
301 std::string rule_path(GetDongleRule());
302 ocpn::replace(cmd, "@PATH@", rule_path.c_str());
303 ocpn::replace(cmd, "@pkexec@", "sudo");
304 auto vbox = new wxBoxSizer(wxVERTICAL);
305 vbox->Add(new ManualInstructions(this, cmd.c_str()));
306 std::string rule_text = GetRule(rule_path);
307 vbox->Add(new ReviewRule(this, rule_text.c_str()));
308 SetAutoLayout(true);
309 SetSizer(vbox);
310 }
311};
312
314class DeviceInfoPanel : public wxPanel {
315public:
316 DeviceInfoPanel(wxWindow* parent, const std::string rule_path)
317 : wxPanel(parent) {
318 std::string cmd(INSTRUCTIONS);
319 ocpn::replace(cmd, "@PATH@", rule_path.c_str());
320 ocpn::replace(cmd, "@pkexec@", "sudo");
321 auto vbox = new wxBoxSizer(wxVERTICAL);
322 vbox->Add(new ManualInstructions(this, cmd.c_str()));
323 vbox->Add(new ReviewRule(this, GetRule(rule_path)));
324 SetAutoLayout(true);
325 SetSizer(vbox);
326 }
327};
328
330class Buttons : public wxPanel {
331public:
332 Buttons(wxWindow* parent, const char* rule_path)
333 : wxPanel(parent), m_rule_path(rule_path) {
334 auto sizer = new wxBoxSizer(wxHORIZONTAL);
335 auto flags = wxSizerFlags().Bottom().Border(wxLEFT);
336 sizer->Add(1, 1, 100, wxEXPAND); // Expanding spacer
337 auto install = new wxButton(this, wxID_ANY, _("Install rule"));
338 install->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
339 [&](wxCommandEvent& ev) { DoInstall(); });
340 install->Enable(getenv("FLATPAK_ID") == NULL);
341 sizer->Add(install, flags);
342 auto quit = new wxButton(this, wxID_EXIT, _("Quit"));
343 quit->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent& ev) {
344 if (getenv("FLATPAK_ID")) {
345 auto flags = wxOK | wxICON_INFORMATION;
346 auto msg = FLATPAK_INSTALL_MSG;
347 OCPNMessageBox(this, msg, _("OpenCPN"), flags);
348 }
349 dynamic_cast<wxDialog*>(GetParent())->EndModal(0);
350 });
351 sizer->Add(quit, flags);
352 SetSizer(sizer);
353 Fit();
354 Show();
355 }
356
357 void DoInstall() {
358 using namespace std;
359 string cmd(INSTRUCTIONS);
360 ocpn::replace(cmd, "@PATH@", m_rule_path);
361 ocpn::replace(cmd, "@pkexec@", "sudo");
362 ifstream f(m_rule_path);
363 auto rule =
364 string(istreambuf_iterator<char>(f), istreambuf_iterator<char>());
365 int sts = system(cmd.c_str());
366 int flags = wxOK | wxICON_WARNING;
367 const char* msg = _("Errors encountered installing rule.");
368 if (WIFEXITED(sts) && WEXITSTATUS(sts) == 0) {
369 if (rule.find("ttyS") != std::string::npos) {
370 msg = RULE_SUCCESS_TTYS_MSG;
371 } else {
372 msg = RULE_SUCCESS_MSG;
373 }
374 flags = wxOK | wxICON_INFORMATION;
375 }
376 OCPNMessageBox(this, msg, _("OpenCPN Info"), flags);
377 }
378
379private:
380 std::string m_rule_path;
381};
382
384class DongleRuleDialog : public wxDialog {
385public:
386 DongleRuleDialog(wxWindow* parent)
387 : wxDialog(parent, wxID_ANY, _("Manage dongle udev rule")) {
388 auto sizer = new wxBoxSizer(wxVERTICAL);
389 auto flags = wxSizerFlags().Expand().Border();
390 std::string intro(DONGLE_INTRO);
391 if (getenv("FLATPAK_ID")) {
392 intro += FLATPAK_INTRO_TRAILER;
393 }
394 sizer->Add(new wxStaticText(this, wxID_ANY, intro), flags);
395 sizer->Add(new wxStaticLine(this), flags);
396 sizer->Add(new DongleInfoPanel(this), flags);
397 sizer->Add(new HidePanel(this, HIDE_DIALOG_LABEL, &hide_dongle_dialog),
398 flags.Left());
399 sizer->Add(new wxStaticLine(this), flags);
400 sizer->Add(new Buttons(this, GetDongleRule().c_str()), flags);
401 SetSizer(sizer);
402 SetAutoLayout(true);
403 Fit();
404 }
405};
406
408static std::string GetDeviceIntro(const char* device, std::string symlink) {
409 std::string intro(DEVICE_INTRO);
410
411 std::string dev_name(device);
412 ocpn::replace(dev_name, "/dev/", "");
413 if (!ocpn::startswith(dev_name, "ttyS")) {
414 intro += DEVICE_LINK_INTRO;
415 }
416 if (getenv("FLATPAK_ID")) {
417 intro += FLATPAK_INTRO_TRAILER;
418 }
419 ocpn::replace(symlink, "/dev/", "");
420 while (intro.find("@SYMLINK@") != std::string::npos) {
421 ocpn::replace(intro, "@SYMLINK@", symlink);
422 }
423 while (intro.find("@DEVICE@") != std::string::npos) {
424 ocpn::replace(intro, "@DEVICE@", dev_name.c_str());
425 }
426 return intro;
427}
428
430class DeviceRuleDialog : public wxDialog {
431public:
432 DeviceRuleDialog(wxWindow* parent, const char* device_path)
433 : wxDialog(parent, wxID_ANY, _("Manage device udev rule")) {
434 auto sizer = new wxBoxSizer(wxVERTICAL);
435 auto flags = wxSizerFlags().Expand().Border();
436
437 std::string symlink(MakeUdevLink());
438 auto intro = GetDeviceIntro(device_path, symlink.c_str());
439 auto rule_path = GetDeviceRule(device_path, symlink.c_str());
440 sizer->Add(new wxStaticText(this, wxID_ANY, intro), flags);
441 sizer->Add(new wxStaticLine(this), flags);
442 sizer->Add(new DeviceInfoPanel(this, rule_path), flags);
443 sizer->Add(new HidePanel(this, HIDE_DIALOG_LABEL, &hide_device_dialog),
444 flags);
445 sizer->Add(new wxStaticLine(this), flags);
446 sizer->Add(new Buttons(this, rule_path.c_str()), flags);
447
448 SetSizer(sizer);
449 SetAutoLayout(true);
450 Fit();
451 }
452};
453
454bool CheckSerialAccess(wxWindow* parent, const std::string device) {
455 if (hide_device_dialog) {
456 return true;
457 }
458 if (!ocpn::exists(device)) {
459 auto& noteman = NotificationManager::GetInstance();
460 std::string msg = "Device not found: ";
461 msg += device;
462 noteman.AddNotification(NotificationSeverity::kInformational, msg, 60);
463 return false;
464 }
465 int result = 0;
466 if (!IsDevicePermissionsOk(device.c_str())) {
467 auto dialog = new DeviceRuleDialog(parent, device.c_str());
468 result = dialog->ShowModal();
469 delete dialog;
470 }
471 return result == 0;
472}
473
474bool CheckDongleAccess(wxWindow* parent) {
475 int result = 0;
476 if (IsDonglePermissionsWrong() && !hide_dongle_dialog) {
477 auto dialog = new DongleRuleDialog(parent);
478 result = dialog->ShowModal();
479 delete dialog;
480 }
481 return result == 0;
482}
483
484#endif // !defined(__linux__) || defined(__ANDROID__)
The Done button.
Non-editable TextCtrl, used like wxStaticText but is copyable.
Definition gui_lib.h:37
An ExpandIcon together with a child window.
void Create(wxWindow *child, std::function< void()> on_resize=[] {})
Second part of two step creation.
wxWindow * GetIcon() const
Return the ExpandableIcon reflecting expanded/collapsed state.
wxWindow * GetChild() const
Return the managed window which is shown or hidden.
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.
bool replace(std::string &str, const std::string &from, const std::string &to)
Perform in place substitution in str, replacing "from" with "to".
bool startswith(const std::string &str, const std::string &prefix)
Return true if s starts with given prefix.
bool exists(const std::string &name)
Class NotificationManager.
Miscellaneous utilities, many of which string related.
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.