OpenCPN Partial API docs
Loading...
Searching...
No Matches
base_platform.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: OpenCPN Platform specific support utilities
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2015 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26#include <wx/wxprec.h>
27
28#include <cstdlib>
29#include <string>
30#include <vector>
31
32#ifdef __MINGW32__
33#undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
34#include <windows.h>
35#endif
36
37#ifndef WX_PRECOMP
38#include <wx/wx.h>
39#endif // precompiled headers
40
41#ifdef __WXMSW__
42#include <windows.h>
43#include <winioctl.h>
44#include <initguid.h>
45#include "setupapi.h" // presently stored in opencpn/src
46#endif
47
48#include <wx/app.h>
49#include <wx/apptrait.h>
50#include <wx/dir.h>
51#include <wx/filename.h>
52#include <wx/platinfo.h>
53#include <wx/stdpaths.h>
54#include <wx/textfile.h>
55#include <wx/tokenzr.h>
56
57#include "config.h"
58
59#include "model/base_platform.h"
60#include "model/cmdline.h"
61#include "model/config_vars.h"
62#include "model/logger.h"
63#include "model/ocpn_utils.h"
64#include "ocpn_plugin.h"
65
66#ifdef __ANDROID__
67#include <QDebug>
68#include "androidUTIL.h"
69#endif
70
71#ifdef __WXOSX__
72#include "model/macutils.h"
73#endif
74
75#ifdef __WXMSW__
76static const char PATH_SEP = ';';
77#else
78static const char PATH_SEP = ':';
79#endif
80
81bool AbstractPlatform::m_isBusy = false;
82
83static const char* const DEFAULT_XDG_DATA_DIRS =
84 "~/.local/share:/usr/local/share:/usr/share";
85
86void appendOSDirSlash(wxString* pString);
87
88BasePlatform* g_BasePlatform;
89
90#ifdef __ANDROID__
91PlatSpec android_plat_spc;
92#endif
93
94static inline bool IsWindows() {
95 return wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS;
96}
97
98static bool checkIfFlatpacked() {
99 wxString id;
100 if (!wxGetEnv("FLATPAK_ID", &id)) {
101 return false;
102 }
103 return id == "org.opencpn.OpenCPN";
104}
105
106static wxString ExpandPaths(wxString paths, AbstractPlatform* platform);
107
108static wxString GetLinuxDataPath() {
109 wxString dirs;
110 if (wxGetEnv("XDG_DATA_DIRS", &dirs)) {
111 dirs = wxString("~/.local/share:") + dirs;
112 } else {
113 dirs = DEFAULT_XDG_DATA_DIRS;
114 }
115 wxString s;
116 wxStringTokenizer tokens(dirs, ':');
117 while (tokens.HasMoreTokens()) {
118 wxString dir = tokens.GetNextToken();
119 if (dir.EndsWith("/")) {
120 dir = dir.SubString(0, dir.length() - 1);
121 }
122 if (!dir.EndsWith("/opencpn/plugins")) {
123 dir += "/opencpn/plugins";
124 }
125 s += dir + (tokens.HasMoreTokens() ? ";" : "");
126 }
127 return s;
128}
129
130static wxString ExpandPaths(wxString paths, AbstractPlatform* platform) {
131 wxStringTokenizer tokens(paths, ';');
132 wxString s = "";
133 while (tokens.HasMoreTokens()) {
134 wxFileName filename(tokens.GetNextToken());
135 filename.Normalize();
136 s += platform->NormalizePath(filename.GetFullPath());
137 if (tokens.HasMoreTokens()) {
138 s += ';';
139 }
140 }
141 return s;
142}
143
144// OCPN Platform implementation
145BasePlatform::BasePlatform() {
146 m_old_logger = 0;
147 m_isFlatpacked = checkIfFlatpacked();
148 m_osDetail = new OCPN_OSDetail;
149 DetectOSDetail(m_osDetail);
150
151#ifdef __ANDROID__
152 androidUtilInit();
153#endif
154
155 InitializeLogFile();
156}
157
158BasePlatform::~BasePlatform() {
159 delete m_osDetail;
160 delete wxLog::SetActiveTarget(new wxLogStderr());
161}
162
163//--------------------------------------------------------------------------
164// Per-Platform file/directory support
165//--------------------------------------------------------------------------
166
167wxStandardPaths& AbstractPlatform::GetStdPaths() {
168#ifndef __ANDROID__
169 return wxStandardPaths::Get();
170#else
171 return *dynamic_cast<wxStandardPaths*>(
172 &(wxTheApp->GetTraits())->GetStandardPaths());
173#endif
174}
175
176wxString AbstractPlatform::NormalizePath(const wxString& full_path) {
177 if (!g_bportable) {
178 return full_path;
179 } else {
180 wxString path(full_path);
181 wxFileName f(path);
182 // If not on another voulme etc. make the portable relative path
183 if (f.MakeRelativeTo(GetPrivateDataDir())) {
184 path = f.GetFullPath();
185 }
186 return path;
187 }
188}
189
190wxString& AbstractPlatform::GetHomeDir() {
191 if (m_homeDir.IsEmpty()) {
192 // Establish a "home" location
193 wxStandardPaths& std_path = GetStdPaths();
194 // TODO Why is the following preferred? Will not compile with gcc...
195 // wxStandardPaths& std_path = wxApp::GetTraits()->GetStandardPaths();
196
197#ifdef __unix__
198 std_path.SetInstallPrefix(wxString(PREFIX, wxConvUTF8));
199#endif
200
201#ifdef __WXMSW__
202 m_homeDir =
203 std_path
204 .GetConfigDir(); // on w98, produces "/windows/Application Data"
205#else
206 m_homeDir = std_path.GetUserConfigDir();
207#endif
208
209#ifdef __ANDROID__
210 m_homeDir = androidGetHomeDir();
211#endif
212
213 if (g_bportable) {
214 wxFileName path(GetExePath());
215 m_homeDir = path.GetPath();
216 }
217
218#ifdef __WXOSX__
219 appendOSDirSlash(&m_homeDir);
220 m_homeDir.Append(_T("opencpn"));
221#endif
222
223 appendOSDirSlash(&m_homeDir);
224 }
225
226 return m_homeDir;
227}
228
229wxString& AbstractPlatform::GetExePath() {
230 if (m_exePath.IsEmpty()) {
231 wxStandardPaths& std_path = GetStdPaths();
232 m_exePath = std_path.GetExecutablePath();
233 }
234
235 return m_exePath;
236}
237
238wxString* AbstractPlatform::GetSharedDataDirPtr() {
239 if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
240 return &m_SData_Dir;
241}
242
244 if (m_PrivateDataDir.IsEmpty()) GetPrivateDataDir();
245 return &m_PrivateDataDir;
246}
247
248wxString& AbstractPlatform::GetSharedDataDir() {
249 if (m_SData_Dir.IsEmpty()) {
250 // Establish a "shared data" location
251 /* From the wxWidgets documentation...
252 *
253 * wxStandardPaths::GetDataDir
254 * wxString GetDataDir() const
255 * Return the location of the applications global, i.e. not
256 * user-specific, data files. Unix: prefix/share/appname Windows: the
257 * directory where the executable file is located Mac:
258 * appname.app/Contents/SharedSupport bundle subdirectory
259 */
260 wxStandardPaths& std_path = GetStdPaths();
261 m_SData_Dir = std_path.GetDataDir();
262 appendOSDirSlash(&m_SData_Dir);
263
264#ifdef __ANDROID__
265 m_SData_Dir = androidGetSharedDir();
266#endif
267
268 if (g_bportable) m_SData_Dir = GetHomeDir();
269 }
270
271 return m_SData_Dir;
272}
273
274wxString GetPluginDataDir(const char* plugin_name) {
275 static const wxString sep = wxFileName::GetPathSeparator();
276
277 wxString datadirs = g_BasePlatform->GetPluginDataPath();
278 wxLogMessage(_T("PlugInManager: Using data dirs from: ") + datadirs);
279 wxStringTokenizer dirs(datadirs, ";");
280 while (dirs.HasMoreTokens()) {
281 wxString dir = dirs.GetNextToken();
282 wxFileName tryDirName(dir);
283 wxDir tryDir;
284 if (!tryDir.Open(tryDirName.GetFullPath())) continue;
285 wxString next;
286 bool more = tryDir.GetFirst(&next);
287 while (more) {
288 if (next == plugin_name) {
289 next = next.Prepend(tryDirName.GetFullPath() + sep);
290 wxLogMessage(_T("PlugInManager: using data dir: %s"), next);
291 return next;
292 }
293 more = tryDir.GetNext(&next);
294 }
295 tryDir.Close();
296 }
297 wxLogMessage(_T("Warning: no data directory found, using \"\""));
298 return "";
299}
300
302 if (!m_PrivateDataDir.IsEmpty() && g_configdir.empty())
303 return m_PrivateDataDir;
304 if (!g_configdir.empty()) {
305 wxString path = g_configdir;
306 if (path.Last() == wxFileName::GetPathSeparator()) path.RemoveLast();
307 m_default_private_datadir = path;
308 return m_default_private_datadir; // FIXME (leamas) normalize and trust
309 // g_configdir
310 }
311 return DefaultPrivateDataDir();
312}
313
315 if (m_PrivateDataDir.IsEmpty()) {
316 // Establish the prefix of the location of user specific data files
317 wxStandardPaths& std_path = GetStdPaths();
318
319#ifdef __WXMSW__
320 m_PrivateDataDir =
321 GetHomeDir(); // should be {Documents and Settings}\......
322#elif defined FLATPAK
323 std::string config_home;
324 if (getenv("XDG_CONFIG_HOME")) {
325 config_home = getenv("XDG_CONFIG_HOME");
326 } else {
327 config_home = getenv("HOME");
328 config_home += "/.var/app/org.opencpn.OpenCPN/config";
329 }
330 m_PrivateDataDir = config_home + "/opencpn";
331
332#elif defined __WXOSX__
333 m_PrivateDataDir =
334 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
335 appendOSDirSlash(&m_PrivateDataDir);
336 m_PrivateDataDir.Append(_T("opencpn"));
337#else
338 m_PrivateDataDir = std_path.GetUserDataDir(); // should be ~/.opencpn
339#endif
340
341 if (g_bportable) m_PrivateDataDir = GetHomeDir();
342 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
343 m_PrivateDataDir.RemoveLast();
344
345#ifdef __ANDROID__
346 m_PrivateDataDir = androidGetPrivateDir();
347#endif
348 }
349 return m_PrivateDataDir;
350}
351
353 if (g_winPluginDir != "") {
354 wxLogMessage("winPluginDir: Using value from ini file.");
355 wxFileName fn(g_winPluginDir);
356 if (!fn.DirExists()) {
357 wxLogWarning("Plugin dir %s does not exist",
358 fn.GetFullPath().mb_str().data());
359 }
360 fn.Normalize();
361 return fn.GetFullPath();
362 }
363 wxString winPluginDir;
364 // Portable case: plugins directory is in the .exe folder
365 if (g_bportable) {
366 winPluginDir = (GetHomeDir() + _T("plugins"));
367 if (ocpn::exists(winPluginDir.ToStdString())) {
368 wxLogMessage("Using portable plugin dir: %s", winPluginDir);
369 return winPluginDir;
370 }
371 }
372 // Standard case: c:\Users\%USERPROFILE%\AppData\Local
373 bool ok = wxGetEnv(_T("LOCALAPPDATA"), &winPluginDir);
374 if (!ok) {
375 wxLogMessage("winPluginDir: Cannot lookup LOCALAPPDATA");
376 // Without %LOCALAPPDATA%: Use default location if it exists.
377 std::string path(wxGetHomeDir().ToStdString());
378 path += "\\AppData\\Local";
379 if (ocpn::exists(path)) {
380 winPluginDir = wxString(path.c_str());
381 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
382 ok = true;
383 }
384 }
385 if (!ok) {
386 // Usually: c:\Users\%USERPROFILE%\AppData\Roaming
387 ok = wxGetEnv(_T("APPDATA"), &winPluginDir);
388 }
389 if (!ok) {
390 // Without %APPDATA%: Use default location if it exists.
391 wxLogMessage("winPluginDir: Cannot lookup APPDATA");
392 std::string path(wxGetHomeDir().ToStdString());
393 path += "\\AppData\\Roaming";
394 if (ocpn::exists(path)) {
395 winPluginDir = wxString(path.c_str());
396 ok = true;
397 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
398 }
399 }
400 if (!ok) {
401 // {Documents and Settings}\.. on W7, else \ProgramData
402 winPluginDir = GetHomeDir();
403 }
404 wxFileName path(winPluginDir);
405 path.Normalize();
406 winPluginDir = path.GetFullPath() + "\\opencpn\\plugins";
407 wxLogMessage("Using private plugin dir: %s", winPluginDir);
408 return winPluginDir;
409}
410
412 if (m_PluginsDir.IsEmpty()) {
413 wxStandardPaths& std_path = GetStdPaths();
414
415 // Get the PlugIns directory location
416 m_PluginsDir = std_path.GetPluginsDir(); // linux: {prefix}/lib/opencpn
417 // Mac: appname.app/Contents/PlugIns
418#ifdef __WXMSW__
419 m_PluginsDir += _T("\\plugins"); // Windows: {exe dir}/plugins
420#endif
421 if (g_bportable) {
422 m_PluginsDir = GetHomeDir();
423 m_PluginsDir += _T("plugins");
424 }
425
426#ifdef __ANDROID__
427 // something like: data/data/org.opencpn.opencpn
428 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
429 fdir.RemoveLastDir();
430 m_PluginsDir = fdir.GetPath();
431#endif
432 }
433 return m_PluginsDir;
434}
435
436wxString* AbstractPlatform::GetPluginDirPtr() {
437 if (m_PluginsDir.IsEmpty()) GetPluginDir();
438 return &m_PluginsDir;
439}
440
441bool AbstractPlatform::isPlatformCapable(int flag) {
442#ifndef __ANDROID__
443 return true;
444#else
445 if (flag == PLATFORM_CAP_PLUGINS) {
446 long platver;
447 wxString tsdk(android_plat_spc.msdk);
448 if (tsdk.ToLong(&platver)) {
449 if (platver >= 11) return true;
450 }
451 } else if (flag == PLATFORM_CAP_FASTPAN) {
452 long platver;
453 wxString tsdk(android_plat_spc.msdk);
454 if (tsdk.ToLong(&platver)) {
455 if (platver >= 14) return true;
456 }
457 }
458
459 return false;
460#endif
461}
462
463void appendOSDirSlash(wxString* pString) {
464 wxChar sep = wxFileName::GetPathSeparator();
465 if (pString->Last() != sep) pString->Append(sep);
466}
467
468wxString AbstractPlatform::GetWritableDocumentsDir() {
469 wxString dir;
470
471#ifdef __ANDROID__
472 dir = androidGetExtStorageDir(); // Used for Chart storage, typically
473#else
474 wxStandardPaths& std_path = GetStdPaths();
475 dir = std_path.GetDocumentsDir();
476#endif
477 return dir;
478}
479
480bool AbstractPlatform::DetectOSDetail(OCPN_OSDetail* detail) {
481 if (!detail) return false;
482
483 // We take some defaults from build-time definitions
484 detail->osd_name = std::string(PKG_TARGET);
485 detail->osd_version = std::string(PKG_TARGET_VERSION);
486
487 // Now parse by basic platform
488#ifdef __linux__
489 if (wxFileExists(_T("/etc/os-release"))) {
490 wxTextFile release_file(_T("/etc/os-release"));
491 if (release_file.Open()) {
492 wxString val;
493 for (wxString str = release_file.GetFirstLine(); !release_file.Eof();
494 str = release_file.GetNextLine()) {
495 if (str.StartsWith(_T("NAME"))) {
496 val = str.AfterFirst('=').Mid(1);
497 val = val.Mid(0, val.Length() - 1);
498 if (val.Length()) detail->osd_name = std::string(val.mb_str());
499 } else if (str.StartsWith(_T("VERSION_ID"))) {
500 val = str.AfterFirst('=').Mid(1);
501 val = val.Mid(0, val.Length() - 1);
502 if (val.Length()) detail->osd_version = std::string(val.mb_str());
503 } else if (str.StartsWith(_T("ID="))) {
504 val = str.AfterFirst('=');
505 if (val.Length()) detail->osd_ID = ocpn::split(val.mb_str(), " ")[0];
506 } else if (str.StartsWith(_T("ID_LIKE"))) {
507 if (val.StartsWith('"')) {
508 val = str.AfterFirst('=').Mid(1);
509 val = val.Mid(0, val.Length() - 1);
510 } else {
511 val = str.AfterFirst('=');
512 }
513
514 if (val.Length()) {
515 detail->osd_names_like = ocpn::split(val.mb_str(), " ");
516 }
517 }
518 }
519
520 release_file.Close();
521 }
522 if (detail->osd_name == _T("Linux Mint")) {
523 if (wxFileExists(_T("/etc/upstream-release/lsb-release"))) {
524 wxTextFile upstream_release_file(
525 _T("/etc/upstream-release/lsb-release"));
526 if (upstream_release_file.Open()) {
527 wxString val;
528 for (wxString str = upstream_release_file.GetFirstLine();
529 !upstream_release_file.Eof();
530 str = upstream_release_file.GetNextLine()) {
531 if (str.StartsWith(_T("DISTRIB_RELEASE"))) {
532 val = str.AfterFirst('=').Mid(0);
533 val = val.Mid(0, val.Length());
534 if (val.Length()) detail->osd_version = std::string(val.mb_str());
535 }
536 }
537 upstream_release_file.Close();
538 }
539 }
540 }
541 }
542#endif
543
544 // Set the default processor architecture
545 detail->osd_arch = std::string("x86_64");
546
547 // then see what is actually running.
548 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
549 wxArchitecture arch = platformInfo.GetArchitecture();
550 if (arch == wxARCH_32) detail->osd_arch = std::string("i386");
551
552#ifdef ocpnARM
553 // arm supports a multiarch runtime environment
554 // That is, the OS may be 64 bit, but OCPN may be built as a 32 bit binary
555 // So, we cannot trust the wxPlatformInfo architecture determination.
556 detail->osd_arch = std::string("arm64");
557#ifdef ocpnARMHF
558 detail->osd_arch = std::string("armhf");
559#endif
560#endif
561
562#ifdef __ANDROID__
563 detail->osd_arch = std::string("arm64");
564 if (arch == wxARCH_32) detail->osd_arch = std::string("armhf");
565#endif
566
567#ifdef __WXOSX__
568 if (IsAppleSilicon() == 1) {
569 if (ProcessIsTranslated() != 1) {
570 detail->osd_arch = std::string("arm64");
571 } else {
572 detail->osd_arch = std::string("x86_64");
573 }
574 } else {
575 detail->osd_arch = std::string("x86_64");
576 }
577#endif
578
579 return true;
580}
581
582wxString& AbstractPlatform::GetConfigFileName() {
583 if (m_config_file_name.IsEmpty()) {
584 // Establish the location of the config file
585 wxStandardPaths& std_path = GetStdPaths();
586
587#ifdef __WXMSW__
588 m_config_file_name = "opencpn.ini";
589 m_config_file_name.Prepend(GetHomeDir());
590
591#elif defined __WXOSX__
592 m_config_file_name =
593 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
594 appendOSDirSlash(&m_config_file_name);
595 m_config_file_name.Append("opencpn");
596 appendOSDirSlash(&m_config_file_name);
597 m_config_file_name.Append("opencpn.ini");
598#elif defined FLATPAK
599 m_config_file_name = GetPrivateDataDir();
600 m_config_file_name.Append("/opencpn.conf");
601 // Usually ~/.var/app/org.opencpn.OpenCPN/config/opencpn.conf
602#else
603 m_config_file_name = std_path.GetUserDataDir(); // should be ~/.opencpn
604 appendOSDirSlash(&m_config_file_name);
605 m_config_file_name.Append("opencpn.conf");
606#endif
607
608 if (g_bportable) {
609 m_config_file_name = GetHomeDir();
610#ifdef __WXMSW__
611 m_config_file_name += "opencpn.ini";
612#elif defined __WXOSX__
613 m_config_file_name += "opencpn.ini";
614#else
615 m_config_file_name += "opencpn.conf";
616#endif
617 }
618
619#ifdef __ANDROID__
620 m_config_file_name = androidGetPrivateDir();
621 appendOSDirSlash(&m_config_file_name);
622 m_config_file_name += "opencpn.conf";
623#endif
624 if (!g_configdir.empty()) {
625 m_config_file_name = g_configdir;
626 m_config_file_name.Append("/opencpn.conf");
627 }
628 }
629 return m_config_file_name;
630}
631
632bool BasePlatform::InitializeLogFile(void) {
633 // Establish Log File location
634 mlog_file = GetPrivateDataDir();
635 appendOSDirSlash(&mlog_file);
636
637#ifdef __WXOSX__
638
639 wxFileName LibPref(mlog_file); // starts like "~/Library/Preferences/opencpn"
640 LibPref.RemoveLastDir(); // takes off "opencpn"
641 LibPref.RemoveLastDir(); // takes off "Preferences"
642
643 mlog_file = LibPref.GetFullPath();
644 appendOSDirSlash(&mlog_file);
645
646 mlog_file.Append(_T("Logs/")); // so, on OS X, opencpn.log ends up in
647 // ~/Library/Logs which makes it accessible to
648 // Applications/Utilities/Console....
649#endif
650
651 // create the opencpn "home" directory if we need to
652 wxFileName wxHomeFiledir(GetHomeDir());
653 if (true != wxHomeFiledir.DirExists(wxHomeFiledir.GetPath()))
654 if (!wxHomeFiledir.Mkdir(wxHomeFiledir.GetPath())) {
655 wxASSERT_MSG(false, _T("Cannot create opencpn home directory"));
656 return false;
657 }
658
659 // create the opencpn "log" directory if we need to
660 wxFileName wxLogFiledir(mlog_file);
661 if (true != wxLogFiledir.DirExists(wxLogFiledir.GetPath())) {
662 if (!wxLogFiledir.Mkdir(wxLogFiledir.GetPath())) {
663 wxASSERT_MSG(false, _T("Cannot create opencpn log directory"));
664 return false;
665 }
666 }
667
668 mlog_file.Append(_T("opencpn.log"));
669 wxString logit = mlog_file;
670
671#ifdef __ANDROID__
672 wxCharBuffer abuf = mlog_file.ToUTF8();
673 qDebug() << "logfile " << abuf.data();
674#endif
675
676 // Constrain the size of the log file
677 if (::wxFileExists(mlog_file)) {
678 if (wxFileName::GetSize(mlog_file) > 1000000) {
679 wxString oldlog = mlog_file;
680 oldlog.Append(_T(".log"));
681 // Defer the showing of this messagebox until the system locale is
682 // established.
683 large_log_message = (_T("Old log will be moved to opencpn.log.log"));
684 ::wxRenameFile(mlog_file, oldlog);
685 }
686 }
687#ifdef __ANDROID__
688 if (::wxFileExists(mlog_file)) {
689 // Force new logfile for each instance
690 // TODO Remove this behaviour on Release
691 ::wxRemoveFile(mlog_file);
692 }
693#endif
694
695 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
696
697 auto logger = new OcpnLog(mlog_file.mb_str());
698 if (m_old_logger) {
699 delete m_old_logger;
700 }
701 m_old_logger = wxLog::SetActiveTarget(logger);
702
703 return true;
704}
705
706void AbstractPlatform::CloseLogFile(void) {
707 if (m_old_logger) {
708 delete wxLog::SetActiveTarget(m_old_logger);
709 m_old_logger = nullptr;
710 }
711}
712
714 if (g_bportable) {
715 wxString sep = wxFileName::GetPathSeparator();
716 wxString ret = GetPrivateDataDir() + sep + _T("plugins");
717 return ret;
718 }
719
720 if (m_pluginDataPath != "") {
721 return m_pluginDataPath;
722 }
723 wxString dirs("");
724#ifdef __ANDROID__
725 wxString pluginDir = GetPrivateDataDir() + "/plugins";
726 dirs += pluginDir;
727#else
728 auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
729 if (isFlatpacked()) {
730 dirs = "~/.var/app/org.opencpn.OpenCPN/data/opencpn/plugins";
731 } else if (osSystemId & wxOS_UNIX_LINUX) {
732 dirs = GetLinuxDataPath();
733 } else if (osSystemId & wxOS_WINDOWS) {
734 dirs = GetWinPluginBaseDir();
735 } else if (osSystemId & wxOS_MAC) {
736 dirs = "/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
737 dirs +=
738 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
739 }
740#endif
741
742 m_pluginDataPath = ExpandPaths(dirs, this);
743 if (m_pluginDataPath != "") {
744 m_pluginDataPath += ";";
745 }
746 m_pluginDataPath += GetPluginDir();
747 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
748 m_pluginDataPath.RemoveLast();
749 }
750 wxLogMessage("Using plugin data path: %s", m_pluginDataPath.mb_str().data());
751 return m_pluginDataPath;
752}
753
754#ifdef __ANDROID__
755void AbstractPlatform::ShowBusySpinner() {
756 if (!m_isBusy) {
757 androidShowBusyIcon();
758 m_isBusy = true;
759 }
760}
761#else
762void AbstractPlatform::ShowBusySpinner() {
763 if (!m_isBusy) {
764 ::wxBeginBusyCursor();
765 m_isBusy = true;
766 }
767}
768#endif
769
770#ifdef __ANDROID__
771void AbstractPlatform::HideBusySpinner() {
772 if (m_isBusy) {
773 androidHideBusyIcon();
774 m_isBusy = false;
775 }
776}
777#else
778void AbstractPlatform::HideBusySpinner() {
779 if (m_isBusy) {
780 ::wxEndBusyCursor();
781 m_isBusy = false;
782 }
783}
784#endif
785
786// getDisplaySize
787
788#if defined(__ANDROID__)
789wxSize BasePlatform::getDisplaySize() { return getAndroidDisplayDimensions(); }
790
791#else
792wxSize BasePlatform::getDisplaySize() { return wxSize(0, 0); }
793#endif
794
795// GetDisplaySizeMM
796double BasePlatform::GetDisplaySizeMM() {
797 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
798 return m_displaySizeMMOverride[0];
799 }
800 double ret = 0;
801
802#ifdef __ANDROID__
803 ret = GetAndroidDisplaySize();
804#endif
805
806 wxLogDebug("Detected display size (horizontal): %d mm", (int)ret);
807 return ret;
808}
809
810#if defined(__ANDROID__)
811double BasePlatform::GetDisplayDPmm() { return getAndroidDPmm(); }
812
813#else
814double BasePlatform::GetDisplayDPmm() {
815 if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
816 double r = getDisplaySize().x; // dots
817 return r / GetDisplaySizeMM();
818 } else {
819 // This is a console app... assuming 300 DPI ~ 12 DPmm
820 return 12.0;
821 }
822}
823#endif
824
826 double rv = 1.0;
827#ifdef __WXMSW__
828 if (win) rv = (double)(win->ToDIP(100)) / 100.;
829#endif
830 return rv;
831}
832
833unsigned int AbstractPlatform::GetSelectRadiusPix() {
834 return GetDisplayDPmm() *
835 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
836}
837
838#ifdef __WXMSW__
839
840#define NAME_SIZE 128
841
842const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
843 0x00, 0x2b, 0xe1, 0x03, 0x18};
844
845// Assumes hDevRegKey is valid
846bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, int* WidthMm,
847 int* HeightMm) {
848 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
849 TCHAR valueName[NAME_SIZE];
850
851 BYTE EDIDdata[1024];
852 DWORD edidsize = sizeof(EDIDdata);
853
854 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
855 ++i) {
856 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
857 &AcutalValueNameLength, NULL, &dwType,
858 EDIDdata, // buffer
859 &edidsize); // buffer size
860
861 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T("EDID")))
862 continue;
863
864 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
865 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
866
867 return true; // valid EDID found
868 }
869
870 return false; // EDID not found
871}
872
873bool GetSizeForDevID(wxString& TargetDevID, int* WidthMm, int* HeightMm) {
874 HDEVINFO devInfo =
875 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR, // class GUID
876 NULL, // enumerator
877 NULL, // HWND
878 DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
879 NULL, // device info, create a new one.
880 NULL, // machine name, local machine
881 NULL); // reserved
882
883 if (NULL == devInfo) return false;
884
885 bool bRes = false;
886
887 for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
888 SP_DEVINFO_DATA devInfoData;
889 memset(&devInfoData, 0, sizeof(devInfoData));
890 devInfoData.cbSize = sizeof(devInfoData);
891
892 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
893 wchar_t Instance[80];
894 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
895 NULL);
896 wxString instance(Instance);
897 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND) continue;
898
899 HKEY hDevRegKey = SetupDiOpenDevRegKey(
900 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
901
902 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) continue;
903
904 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
905
906 RegCloseKey(hDevRegKey);
907 }
908 }
909 SetupDiDestroyDeviceInfoList(devInfo);
910 return bRes;
911}
912
913bool AbstractPlatform::GetWindowsMonitorSize(int* width, int* height) {
914 bool bFoundDevice = true;
915
916 if (m_monitorWidth < 10) {
917 int WidthMm = 0;
918 int HeightMm = 0;
919
920 DISPLAY_DEVICE dd;
921 dd.cb = sizeof(dd);
922 DWORD dev = 0; // device index
923 int id = 1; // monitor number, as used by Display Properties > Settings
924
925 wxString DeviceID;
926 bFoundDevice = false;
927 while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice) {
928 DISPLAY_DEVICE ddMon;
929 ZeroMemory(&ddMon, sizeof(ddMon));
930 ddMon.cb = sizeof(ddMon);
931 DWORD devMon = 0;
932
933 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
934 !bFoundDevice) {
935 if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
936 !(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
937 DeviceID = wxString(ddMon.DeviceID, wxConvUTF8);
938 DeviceID = DeviceID.Mid(8);
939 DeviceID = DeviceID.Mid(0, DeviceID.Find('\\'));
940
941 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
942 }
943 devMon++;
944
945 ZeroMemory(&ddMon, sizeof(ddMon));
946 ddMon.cb = sizeof(ddMon);
947 }
948
949 ZeroMemory(&dd, sizeof(dd));
950 dd.cb = sizeof(dd);
951 dev++;
952 }
953 m_monitorWidth = WidthMm;
954 m_monitorHeight = HeightMm;
955 }
956
957 if (width) *width = m_monitorWidth;
958 if (height) *height = m_monitorHeight;
959
960 return bFoundDevice;
961}
962
963#endif // __WXMSW__
964
965int BasePlatform::GetSvgStdIconSize(const wxWindow* w, bool touch) {
966 double size = w->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
967#if wxCHECK_VERSION(3, 1, 2)
968 // Apply scale factor, mostly for Windows. Other platforms
969 // does this in the toolkits, ToDIP() is aware of this.
970 size *= static_cast<double>(w->ToDIP(100)) / 100.;
971#endif
972 // Force minimum physical size for touch screens
973 if (touch) {
974 double pixel_per_mm = wxGetDisplaySize().x / GetDisplaySizeMM();
975 size = std::max(size, 7.0 * pixel_per_mm);
976 }
977 return std::round(size);
978}
wxString * GetPrivateDataDirPtr()
Legacy compatibility syntactic sugar for GetPrivateDataDir().
wxString & GetPluginDir()
The original in-tree plugin directory, sometimes not user-writable.
wxString GetWinPluginBaseDir()
Base directory for user writable windows plugins, reflects winPluginDir option, defaults to LOCALAPPD...
wxString & DefaultPrivateDataDir()
Return dir path for opencpn.log, etc., does not respect -c option.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
wxString GetPluginDataPath()
Return ';'-separated list of base directories for plugin data.
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
int GetSvgStdIconSize(const wxWindow *w, bool touch) override
Return icon size roughly corresponding to height of a char in w, tweaked to be "big enough" for touch...
Customized logger class appending to a file providing:
Definition logger.h:88
Global variables reflecting command line options and arguments.
Enhanced logging interface on top of wx/log.h.
std::vector< std::string > split(const char *token_string, const std::string &delimiter)
Return vector of items in s separated by delimiter.
bool exists(const std::string &name)
PlugIn Object Definition/API.
Miscellaneous utilities, many of which string related.