OpenCPN Partial API docs
Loading...
Searching...
No Matches
base_platform.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2015 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <algorithm>
25#include <cstdlib>
26#include <string>
27#include <vector>
28
29#ifdef __APPLE__
30#include <malloc/malloc.h>
31#include <mach/vm_map.h>
32#endif
33
34#ifdef __ANDROID__
35#include <QDebug>
36#include "androidUTIL.h"
37#endif
38
39#ifdef __MINGW32__
40#undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
41#include <windows.h>
42#endif
43
44#ifdef _WIN32
45#define NOMINMAX // required to not interfere with std::max (sigh)
46#include <winsock2.h>
47#include <windows.h>
48#include <winioctl.h>
49#include <initguid.h>
50#include <psapi.h>
51#include "setupapi.h" // presently stored in opencpn/src
52#endif
53
54#if defined(__linux__)
55#include <sys/types.h>
56#include <sys/sysinfo.h>
57#endif
58
59#include <wx/wxprec.h>
60#ifndef WX_PRECOMP
61#include <wx/wx.h>
62#endif
63
64#include <wx/app.h>
65#include <wx/apptrait.h>
66#include <wx/dir.h>
67#include <wx/filename.h>
68#include <wx/platinfo.h>
69#include <wx/stdpaths.h>
70#include <wx/textfile.h>
71#include <wx/tokenzr.h>
72
73#include "config.h"
74
75#include "model/base_platform.h"
76#include "model/cmdline.h"
77#include "model/config_vars.h"
78#include "model/gui_vars.h"
79#include "model/logger.h"
80#include "model/ocpn_utils.h"
81#include "ocpn_plugin.h"
82
83#ifdef __WXOSX__
84#include "model/macutils.h"
85#endif
86
87#ifdef __WXMSW__
88static const char PATH_SEP = ';';
89#else
90static const char PATH_SEP = ':';
91#endif
92
93bool AbstractPlatform::m_isBusy = false;
94
95static const char* const DEFAULT_XDG_DATA_DIRS =
96 "~/.local/share:/usr/local/share:/usr/share";
97
98void appendOSDirSlash(wxString* pString);
99
101
102#ifdef __ANDROID__
103PlatSpec android_plat_spc;
104#endif
105
106static inline bool IsWindows() {
107 return wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS;
108}
109
110static bool checkIfFlatpacked() {
111 wxString id;
112 if (!wxGetEnv("FLATPAK_ID", &id)) {
113 return false;
114 }
115 return id == "org.opencpn.OpenCPN";
116}
117
118static wxString ExpandPaths(wxString paths, AbstractPlatform* platform);
119
120static wxString GetLinuxDataPath() {
121 wxString dirs;
122 if (wxGetEnv("XDG_DATA_DIRS", &dirs)) {
123 dirs = wxString("~/.local/share:") + dirs;
124 } else {
125 dirs = DEFAULT_XDG_DATA_DIRS;
126 }
127 wxString s;
128 wxStringTokenizer tokens(dirs, ':');
129 while (tokens.HasMoreTokens()) {
130 wxString dir = tokens.GetNextToken();
131 if (dir.EndsWith("/")) {
132 dir = dir.SubString(0, dir.length() - 1);
133 }
134 if (!dir.EndsWith("/opencpn/plugins")) {
135 dir += "/opencpn/plugins";
136 }
137 s += dir + (tokens.HasMoreTokens() ? ";" : "");
138 }
139 return s;
140}
141
142static wxString ExpandPaths(wxString paths, AbstractPlatform* platform) {
143 wxStringTokenizer tokens(paths, ';');
144 wxString s = "";
145 while (tokens.HasMoreTokens()) {
146 wxFileName filename(tokens.GetNextToken());
147 filename.Normalize();
148 s += platform->NormalizePath(filename.GetFullPath());
149 if (tokens.HasMoreTokens()) {
150 s += ';';
151 }
152 }
153 return s;
154}
155
156// OCPN Platform implementation
157BasePlatform::BasePlatform() {
158 m_old_logger = 0;
159 m_isFlatpacked = checkIfFlatpacked();
160 m_osDetail = new OCPN_OSDetail;
161 DetectOSDetail(m_osDetail);
162
163#ifdef __ANDROID__
164 androidUtilInit();
165#endif
166
167 InitializeLogFile();
168}
169
170BasePlatform::~BasePlatform() {
171 delete m_osDetail;
172 delete wxLog::SetActiveTarget(new wxLogStderr());
173}
174
175//--------------------------------------------------------------------------
176// Per-Platform file/directory support
177//--------------------------------------------------------------------------
178
179wxStandardPaths& AbstractPlatform::GetStdPaths() {
180#ifndef __ANDROID__
181 return wxStandardPaths::Get();
182#else
183 return *dynamic_cast<wxStandardPaths*>(
184 &(wxTheApp->GetTraits())->GetStandardPaths());
185#endif
186}
187
188wxString AbstractPlatform::NormalizePath(const wxString& full_path) {
189 if (!g_bportable) {
190 return full_path;
191 } else {
192 wxString path(full_path);
193 wxFileName f(path);
194 // If not on another voulme etc. make the portable relative path
195 if (f.MakeRelativeTo(GetPrivateDataDir())) {
196 path = f.GetFullPath();
197 }
198 return path;
199 }
200}
201
202wxString& AbstractPlatform::GetHomeDir() {
203 if (m_homeDir.IsEmpty()) {
204 // Establish a "home" location
205 wxStandardPaths& std_path = GetStdPaths();
206 // TODO Why is the following preferred? Will not compile with gcc...
207 // wxStandardPaths& std_path = wxApp::GetTraits()->GetStandardPaths();
208
209#ifdef __unix__
210 std_path.SetInstallPrefix(wxString(PREFIX, wxConvUTF8));
211#endif
212
213#ifdef __WXMSW__
214 m_homeDir =
215 std_path
216 .GetConfigDir(); // on w98, produces "/windows/Application Data"
217#else
218 m_homeDir = std_path.GetUserConfigDir();
219#endif
220
221#ifdef __ANDROID__
222 m_homeDir = androidGetHomeDir();
223#endif
224
225 if (g_bportable) {
226 wxFileName path(GetExePath());
227 m_homeDir = path.GetPath();
228 }
229
230#ifdef __WXOSX__
231 appendOSDirSlash(&m_homeDir);
232 m_homeDir.Append("opencpn");
233#endif
234
235 appendOSDirSlash(&m_homeDir);
236 }
237
238 return m_homeDir;
239}
240
241wxString& AbstractPlatform::GetExePath() {
242 if (m_exePath.IsEmpty()) {
243 wxStandardPaths& std_path = GetStdPaths();
244 m_exePath = std_path.GetExecutablePath();
245 }
246
247 return m_exePath;
248}
249
250wxString* AbstractPlatform::GetSharedDataDirPtr() {
251 if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
252 return &m_SData_Dir;
253}
254
256 if (m_PrivateDataDir.IsEmpty()) GetPrivateDataDir();
257 return &m_PrivateDataDir;
258}
259
260wxString& AbstractPlatform::GetSharedDataDir() {
261 if (m_SData_Dir.IsEmpty()) {
262 // Establish a "shared data" location
263 /* From the wxWidgets documentation...
264 *
265 * wxStandardPaths::GetDataDir
266 * wxString GetDataDir() const
267 * Return the location of the applications global, i.e. not
268 * user-specific, data files. Unix: prefix/share/appname Windows: the
269 * directory where the executable file is located Mac:
270 * appname.app/Contents/SharedSupport bundle subdirectory
271 */
272 wxStandardPaths& std_path = GetStdPaths();
273 m_SData_Dir = std_path.GetDataDir();
274 appendOSDirSlash(&m_SData_Dir);
275
276#ifdef __ANDROID__
277 m_SData_Dir = androidGetSharedDir();
278#endif
279
280 if (g_bportable) m_SData_Dir = GetHomeDir();
281 }
282
283 return m_SData_Dir;
284}
285
287 wxString lic;
288#ifdef __ANDROID__
289 lic = androidGetSupplementalLicense();
290#endif
291 return lic;
292}
293
294wxString GetPluginDataDir(const char* plugin_name) {
295 static const wxString sep = wxFileName::GetPathSeparator();
296
297 wxString datadirs = g_BasePlatform->GetPluginDataPath();
298 wxLogMessage("PlugInManager: Using data dirs from: " + datadirs);
299 wxStringTokenizer dirs(datadirs, ";");
300 while (dirs.HasMoreTokens()) {
301 wxString dir = dirs.GetNextToken();
302 wxFileName tryDirName(dir);
303 wxDir tryDir;
304 if (!tryDir.Open(tryDirName.GetFullPath())) continue;
305 wxString next;
306 bool more = tryDir.GetFirst(&next);
307 while (more) {
308 if (next == plugin_name) {
309 next = next.Prepend(tryDirName.GetFullPath() + sep);
310 wxLogMessage("PlugInManager: using data dir: %s", next);
311 return next;
312 }
313 more = tryDir.GetNext(&next);
314 }
315 tryDir.Close();
316 }
317 wxLogMessage("Warning: no data directory found, using \"\"");
318 return "";
319}
320
322 if (!m_PrivateDataDir.IsEmpty() && g_configdir.empty())
323 return m_PrivateDataDir;
324 if (!g_configdir.empty()) {
325 wxString path = g_configdir;
326 if (path.Last() == wxFileName::GetPathSeparator()) path.RemoveLast();
327 m_default_private_datadir = path;
328 return m_default_private_datadir; // FIXME (leamas) normalize and trust
329 // g_configdir
330 }
331 return DefaultPrivateDataDir();
332}
333
335 if (m_PrivateDataDir.IsEmpty()) {
336 // Establish the prefix of the location of user specific data files
337 wxStandardPaths& std_path = GetStdPaths();
338
339#ifdef __WXMSW__
340 m_PrivateDataDir =
341 GetHomeDir(); // should be {Documents and Settings}\......
342#elif defined FLATPAK
343 std::string config_home;
344 if (getenv("XDG_CONFIG_HOME")) {
345 config_home = getenv("XDG_CONFIG_HOME");
346 } else {
347 config_home = getenv("HOME");
348 config_home += "/.var/app/org.opencpn.OpenCPN/config";
349 }
350 m_PrivateDataDir = config_home + "/opencpn";
351
352#elif defined __WXOSX__
353 m_PrivateDataDir =
354 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
355 appendOSDirSlash(&m_PrivateDataDir);
356 m_PrivateDataDir.Append("opencpn");
357#else
358 if (getenv("OCPN_TEST_HOMEDIR"))
359 m_PrivateDataDir = getenv("OCPN_TEST_HOMEDIR");
360 else
361 m_PrivateDataDir = std_path.GetUserDataDir(); // should be ~/.opencpn
362#endif
363
364 if (g_bportable) m_PrivateDataDir = GetHomeDir();
365 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
366 m_PrivateDataDir.RemoveLast();
367
368#ifdef __ANDROID__
369 m_PrivateDataDir = androidGetPrivateDir();
370#endif
371 }
372 return m_PrivateDataDir;
373}
374
376 if (g_winPluginDir != "") {
377 wxLogMessage("winPluginDir: Using value from ini file.");
378 wxFileName fn(g_winPluginDir);
379 if (!fn.DirExists()) {
380 wxLogWarning("Plugin dir %s does not exist",
381 fn.GetFullPath().mb_str().data());
382 }
383 fn.Normalize();
384 return fn.GetFullPath();
385 }
386 wxString winPluginDir;
387 // Portable case: plugins directory is in the .exe folder
388 if (g_bportable) {
389 winPluginDir = (GetHomeDir() + "plugins");
390 if (ocpn::exists(winPluginDir.ToStdString())) {
391 wxLogMessage("Using portable plugin dir: %s", winPluginDir);
392 return winPluginDir;
393 }
394 }
395 // Standard case: c:\Users\%USERPROFILE%\AppData\Local
396 bool ok = wxGetEnv("LOCALAPPDATA", &winPluginDir);
397 if (!ok) {
398 wxLogMessage("winPluginDir: Cannot lookup LOCALAPPDATA");
399 // Without %LOCALAPPDATA%: Use default location if it exists.
400 std::string path(wxGetHomeDir().ToStdString());
401 path += "\\AppData\\Local";
402 if (ocpn::exists(path)) {
403 winPluginDir = wxString(path.c_str());
404 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
405 ok = true;
406 }
407 }
408 if (!ok) {
409 // Usually: c:\Users\%USERPROFILE%\AppData\Roaming
410 ok = wxGetEnv("APPDATA", &winPluginDir);
411 }
412 if (!ok) {
413 // Without %APPDATA%: Use default location if it exists.
414 wxLogMessage("winPluginDir: Cannot lookup APPDATA");
415 std::string path(wxGetHomeDir().ToStdString());
416 path += "\\AppData\\Roaming";
417 if (ocpn::exists(path)) {
418 winPluginDir = wxString(path.c_str());
419 ok = true;
420 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
421 }
422 }
423 if (!ok) {
424 // {Documents and Settings}\.. on W7, else \ProgramData
425 winPluginDir = GetHomeDir();
426 }
427 wxFileName path(winPluginDir);
428 path.Normalize();
429 winPluginDir = path.GetFullPath() + "\\opencpn\\plugins";
430 wxLogMessage("Using private plugin dir: %s", winPluginDir);
431 return winPluginDir;
432}
433
435 if (m_PluginsDir.IsEmpty()) {
436 wxStandardPaths& std_path = GetStdPaths();
437
438 // Get the PlugIns directory location
439 m_PluginsDir = std_path.GetPluginsDir(); // linux: {prefix}/lib/opencpn
440 // Mac: appname.app/Contents/PlugIns
441#ifdef __WXMSW__
442 m_PluginsDir += "\\plugins"; // Windows: {exe dir}/plugins
443#endif
444 if (g_bportable) {
445 m_PluginsDir = GetHomeDir();
446 m_PluginsDir += "plugins";
447 }
448
449#ifdef __ANDROID__
450 // something like: data/data/org.opencpn.opencpn
451 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
452 fdir.RemoveLastDir();
453 m_PluginsDir = fdir.GetPath();
454#endif
455 }
456 return m_PluginsDir;
457}
458
459wxString* AbstractPlatform::GetPluginDirPtr() {
460 if (m_PluginsDir.IsEmpty()) GetPluginDir();
461 return &m_PluginsDir;
462}
463
464bool AbstractPlatform::isPlatformCapable(int flag) {
465#ifndef __ANDROID__
466 return true;
467#else
468 if (flag == PLATFORM_CAP_PLUGINS) {
469 long platver;
470 wxString tsdk(android_plat_spc.msdk);
471 if (tsdk.ToLong(&platver)) {
472 if (platver >= 11) return true;
473 }
474 } else if (flag == PLATFORM_CAP_FASTPAN) {
475 long platver;
476 wxString tsdk(android_plat_spc.msdk);
477 if (tsdk.ToLong(&platver)) {
478 if (platver >= 14) return true;
479 }
480 }
481
482 return false;
483#endif
484}
485
486void appendOSDirSlash(wxString* pString) {
487 wxChar sep = wxFileName::GetPathSeparator();
488 if (pString->Last() != sep) pString->Append(sep);
489}
490
491wxString AbstractPlatform::GetWritableDocumentsDir() {
492 wxString dir;
493
494#ifdef __ANDROID__
495 dir = androidGetExtStorageDir(); // Used for Chart storage, typically
496#else
497 wxStandardPaths& std_path = GetStdPaths();
498 dir = std_path.GetDocumentsDir();
499#endif
500 return dir;
501}
502
503bool AbstractPlatform::DetectOSDetail(OCPN_OSDetail* detail) {
504 if (!detail) return false;
505
506 // We take some defaults from build-time definitions
507 detail->osd_name = std::string(PKG_TARGET);
508 detail->osd_version = std::string(PKG_TARGET_VERSION);
509
510 // Now parse by basic platform
511#ifdef __linux__
512 if (wxFileExists("/etc/os-release")) {
513 wxTextFile release_file("/etc/os-release");
514 if (release_file.Open()) {
515 wxString val;
516 for (wxString str = release_file.GetFirstLine(); !release_file.Eof();
517 str = release_file.GetNextLine()) {
518 if (str.StartsWith("NAME")) {
519 val = str.AfterFirst('=').Mid(1);
520 val = val.Mid(0, val.Length() - 1);
521 if (val.Length()) detail->osd_name = std::string(val.mb_str());
522 } else if (str.StartsWith("VERSION_ID")) {
523 val = str.AfterFirst('=').Mid(1);
524 val = val.Mid(0, val.Length() - 1);
525 if (val.Length()) detail->osd_version = std::string(val.mb_str());
526 } else if (str.StartsWith("ID=")) {
527 val = str.AfterFirst('=');
528 if (val.Length()) detail->osd_ID = ocpn::split(val.mb_str(), " ")[0];
529 } else if (str.StartsWith("ID_LIKE")) {
530 if (val.StartsWith('"')) {
531 val = str.AfterFirst('=').Mid(1);
532 val = val.Mid(0, val.Length() - 1);
533 } else {
534 val = str.AfterFirst('=');
535 }
536
537 if (val.Length()) {
538 detail->osd_names_like = ocpn::split(val.mb_str(), " ");
539 }
540 }
541 }
542
543 release_file.Close();
544 }
545 if (detail->osd_name == "Linux Mint") {
546 if (wxFileExists("/etc/upstream-release/lsb-release")) {
547 wxTextFile upstream_release_file("/etc/upstream-release/lsb-release");
548 if (upstream_release_file.Open()) {
549 wxString val;
550 for (wxString str = upstream_release_file.GetFirstLine();
551 !upstream_release_file.Eof();
552 str = upstream_release_file.GetNextLine()) {
553 if (str.StartsWith("DISTRIB_RELEASE")) {
554 val = str.AfterFirst('=').Mid(0);
555 val = val.Mid(0, val.Length());
556 if (val.Length()) detail->osd_version = std::string(val.mb_str());
557 }
558 }
559 upstream_release_file.Close();
560 }
561 }
562 }
563 }
564#endif
565
566 // Set the default processor architecture
567 detail->osd_arch = std::string("x86_64");
568
569 // then see what is actually running.
570 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
571 wxArchitecture arch = platformInfo.GetArchitecture();
572 if (arch == wxARCH_32) detail->osd_arch = std::string("i386");
573
574#ifdef ocpnARM
575 // arm supports a multiarch runtime environment
576 // That is, the OS may be 64 bit, but OCPN may be built as a 32 bit binary
577 // So, we cannot trust the wxPlatformInfo architecture determination.
578 detail->osd_arch = std::string("arm64");
579#ifdef ocpnARMHF
580 detail->osd_arch = std::string("armhf");
581#endif
582#endif
583
584#ifdef __ANDROID__
585 detail->osd_arch = std::string("arm64");
586 if (arch == wxARCH_32) detail->osd_arch = std::string("armhf");
587#endif
588
589#ifdef __WXOSX__
590 if (IsAppleSilicon() == 1) {
591 if (ProcessIsTranslated() != 1) {
592 detail->osd_arch = std::string("arm64");
593 } else {
594 detail->osd_arch = std::string("x86_64");
595 }
596 } else {
597 detail->osd_arch = std::string("x86_64");
598 }
599#endif
600
601 return true;
602}
603
604wxString& AbstractPlatform::GetConfigFileName() {
605 if (m_config_file_name.IsEmpty()) {
606 // Establish the location of the config file
607 wxStandardPaths& std_path = GetStdPaths();
608
609#ifdef __WXMSW__
610 m_config_file_name = "opencpn.ini";
611 m_config_file_name.Prepend(GetHomeDir());
612
613#elif defined __WXOSX__
614 m_config_file_name =
615 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
616 appendOSDirSlash(&m_config_file_name);
617 m_config_file_name.Append("opencpn");
618 appendOSDirSlash(&m_config_file_name);
619 m_config_file_name.Append("opencpn.ini");
620#elif defined FLATPAK
621 m_config_file_name = GetPrivateDataDir();
622 m_config_file_name.Append("/opencpn.conf");
623 // Usually ~/.var/app/org.opencpn.OpenCPN/config/opencpn.conf
624#else
625 m_config_file_name = std_path.GetUserDataDir(); // should be ~/.opencpn
626 appendOSDirSlash(&m_config_file_name);
627 m_config_file_name.Append("opencpn.conf");
628#endif
629
630 if (g_bportable) {
631 m_config_file_name = GetHomeDir();
632#ifdef __WXMSW__
633 m_config_file_name += "opencpn.ini";
634#elif defined __WXOSX__
635 m_config_file_name += "opencpn.ini";
636#else
637 m_config_file_name += "opencpn.conf";
638#endif
639 }
640
641#ifdef __ANDROID__
642 m_config_file_name = androidGetPrivateDir();
643 appendOSDirSlash(&m_config_file_name);
644 m_config_file_name += "opencpn.conf";
645#endif
646 if (!g_configdir.empty()) {
647 m_config_file_name = g_configdir;
648 m_config_file_name.Append("/opencpn.conf");
649 }
650 }
651 return m_config_file_name;
652}
653
654bool BasePlatform::InitializeLogFile() {
655 // Establish Log File location
656 mlog_file = GetPrivateDataDir();
657 appendOSDirSlash(&mlog_file);
658
659#ifdef __WXOSX__
660
661 wxFileName LibPref(mlog_file); // starts like "~/Library/Preferences/opencpn"
662 LibPref.RemoveLastDir(); // takes off "opencpn"
663 LibPref.RemoveLastDir(); // takes off "Preferences"
664
665 mlog_file = LibPref.GetFullPath();
666 appendOSDirSlash(&mlog_file);
667
668 mlog_file.Append("Logs/"); // so, on OS X, opencpn.log ends up in
669 // ~/Library/Logs which makes it accessible to
670 // Applications/Utilities/Console....
671#endif
672
673 // create the opencpn "home" directory if we need to
674 wxFileName wxHomeFiledir(GetHomeDir());
675 if (true != wxHomeFiledir.DirExists(wxHomeFiledir.GetPath()))
676 if (!wxHomeFiledir.Mkdir(wxHomeFiledir.GetPath())) {
677 wxASSERT_MSG(false, "Cannot create opencpn home directory");
678 return false;
679 }
680
681 // create the opencpn "log" directory if we need to
682 wxFileName wxLogFiledir(mlog_file);
683 if (true != wxLogFiledir.DirExists(wxLogFiledir.GetPath())) {
684 if (!wxLogFiledir.Mkdir(wxLogFiledir.GetPath())) {
685 wxASSERT_MSG(false, wxString("Cannot create opencpn log directory: ") +
686 wxLogFiledir.GetPath());
687 return false;
688 }
689 }
690
691 mlog_file.Append("opencpn.log");
692 wxString logit = mlog_file;
693
694#ifdef __ANDROID__
695 wxCharBuffer abuf = mlog_file.ToUTF8();
696 qDebug() << "logfile " << abuf.data();
697#endif
698
699 // Constrain the size of the log file
700 if (::wxFileExists(mlog_file)) {
701 if (wxFileName::GetSize(mlog_file) > 1000000) {
702 wxString oldlog = mlog_file;
703 oldlog.Append(".log");
704 // Defer the showing of this messagebox until the system locale is
705 // established.
706 large_log_message = ("Old log will be moved to opencpn.log.log");
707 ::wxRenameFile(mlog_file, oldlog);
708 }
709 }
710#ifdef __ANDROID__
711 if (::wxFileExists(mlog_file)) {
712 // Force new logfile for each instance
713 // TODO Remove this behaviour on Release
714 ::wxRemoveFile(mlog_file);
715 }
716#endif
717
718 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
719
720 auto logger = new OcpnLog(mlog_file.mb_str());
721 if (m_old_logger) {
722 delete m_old_logger;
723 }
724 m_old_logger = wxLog::SetActiveTarget(logger);
725
726 return true;
727}
728
729void AbstractPlatform::CloseLogFile() {
730 if (m_old_logger) {
731 delete wxLog::SetActiveTarget(m_old_logger);
732 m_old_logger = nullptr;
733 }
734}
735
737 if (g_bportable) {
738 wxString sep = wxFileName::GetPathSeparator();
739 wxString ret = GetPrivateDataDir() + sep + "plugins";
740 return ret;
741 }
742
743 if (m_pluginDataPath != "") {
744 return m_pluginDataPath;
745 }
746 wxString dirs("");
747#ifdef __ANDROID__
748 wxString pluginDir = GetPrivateDataDir() + "/plugins";
749 dirs += pluginDir;
750#else
751 auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
752 if (isFlatpacked()) {
753 dirs = "~/.var/app/org.opencpn.OpenCPN/data/opencpn/plugins";
754 } else if (osSystemId & wxOS_UNIX_LINUX) {
755 dirs = GetLinuxDataPath();
756 } else if (osSystemId & wxOS_WINDOWS) {
757 dirs = GetWinPluginBaseDir();
758 } else if (osSystemId & wxOS_MAC) {
759 dirs = "/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
760 dirs +=
761 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
762 }
763#endif
764
765 m_pluginDataPath = ExpandPaths(dirs, this);
766 if (m_pluginDataPath != "") {
767 m_pluginDataPath += ";";
768 }
769 m_pluginDataPath += GetPluginDir();
770 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
771 m_pluginDataPath.RemoveLast();
772 }
773 wxLogMessage("Using plugin data path: %s", m_pluginDataPath.mb_str().data());
774 return m_pluginDataPath;
775}
776
777#ifdef __ANDROID__
778void AbstractPlatform::ShowBusySpinner() {
779 if (!m_isBusy) {
780 androidShowBusyIcon();
781 m_isBusy = true;
782 }
783}
784#else
785void AbstractPlatform::ShowBusySpinner() {
786 if (!m_isBusy) {
787 ::wxBeginBusyCursor();
788 m_isBusy = true;
789 }
790}
791#endif
792
793#ifdef __ANDROID__
794void AbstractPlatform::HideBusySpinner() {
795 if (m_isBusy) {
796 androidHideBusyIcon();
797 m_isBusy = false;
798 }
799}
800#else
801void AbstractPlatform::HideBusySpinner() {
802 if (m_isBusy) {
803 ::wxEndBusyCursor();
804 m_isBusy = false;
805 }
806}
807#endif
808
809// getDisplaySize
810
811#if defined(__ANDROID__)
812wxSize BasePlatform::getDisplaySize() { return getAndroidDisplayDimensions(); }
813
814#else
815wxSize BasePlatform::getDisplaySize() { return wxSize(0, 0); }
816#endif
817
818// GetDisplaySizeMM
819double BasePlatform::GetDisplaySizeMM() {
820 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
821 return m_displaySizeMMOverride[0];
822 }
823 double ret = 0;
824
825#ifdef __ANDROID__
826 ret = GetAndroidDisplaySize();
827#endif
828
829 wxLogDebug("Detected display size (horizontal): %d mm", (int)ret);
830 return ret;
831}
832
833#if defined(__ANDROID__)
834double BasePlatform::GetDisplayDPmm() { return getAndroidDPmm(); }
835
836#else
837double BasePlatform::GetDisplayDPmm() {
838 if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
839 double r = getDisplaySize().x; // dots
840 return r / GetDisplaySizeMM();
841 } else {
842 // This is a console app... assuming 300 DPI ~ 12 DPmm
843 return 12.0;
844 }
845}
846#endif
847
849 double rv = 1.0;
850#ifdef __WXMSW__
851 if (win) rv = (double)(win->ToDIP(100)) / 100.;
852#endif
853 return rv;
854}
855
856unsigned int AbstractPlatform::GetSelectRadiusPix() {
857 return GetDisplayDPmm() *
858 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
859}
860
861#ifdef __WXMSW__
862
863#define NAME_SIZE 128
864
865const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
866 0x00, 0x2b, 0xe1, 0x03, 0x18};
867
868// Assumes hDevRegKey is valid
869bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, int* WidthMm,
870 int* HeightMm) {
871 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
872 TCHAR valueName[NAME_SIZE];
873
874 BYTE EDIDdata[1024];
875 DWORD edidsize = sizeof(EDIDdata);
876
877 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
878 ++i) {
879 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
880 &AcutalValueNameLength, NULL, &dwType,
881 EDIDdata, // buffer
882 &edidsize); // buffer size
883
884 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, L"EDID")) continue;
885
886 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
887 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
888
889 return true; // valid EDID found
890 }
891
892 return false; // EDID not found
893}
894
895bool GetSizeForDevID(wxString& TargetDevID, int* WidthMm, int* HeightMm) {
896 HDEVINFO devInfo =
897 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR, // class GUID
898 NULL, // enumerator
899 NULL, // HWND
900 DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
901 NULL, // device info, create a new one.
902 NULL, // machine name, local machine
903 NULL); // reserved
904
905 if (NULL == devInfo) return false;
906
907 bool bRes = false;
908
909 for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
910 SP_DEVINFO_DATA devInfoData;
911 memset(&devInfoData, 0, sizeof(devInfoData));
912 devInfoData.cbSize = sizeof(devInfoData);
913
914 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
915 wchar_t Instance[80];
916 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
917 NULL);
918 wxString instance(Instance);
919 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND) continue;
920
921 HKEY hDevRegKey = SetupDiOpenDevRegKey(
922 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
923
924 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) continue;
925
926 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
927
928 RegCloseKey(hDevRegKey);
929 }
930 }
931 SetupDiDestroyDeviceInfoList(devInfo);
932 return bRes;
933}
934
935bool AbstractPlatform::GetWindowsMonitorSize(int* width, int* height) {
936 bool bFoundDevice = true;
937
938 if (m_monitorWidth < 10) {
939 int WidthMm = 0;
940 int HeightMm = 0;
941
942 DISPLAY_DEVICE dd;
943 dd.cb = sizeof(dd);
944 DWORD dev = 0; // device index
945 int id = 1; // monitor number, as used by Display Properties > Settings
946
947 wxString DeviceID;
948 bFoundDevice = false;
949 while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice) {
950 DISPLAY_DEVICE ddMon;
951 ZeroMemory(&ddMon, sizeof(ddMon));
952 ddMon.cb = sizeof(ddMon);
953 DWORD devMon = 0;
954
955 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
956 !bFoundDevice) {
957 if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
958 !(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
959 DeviceID = wxString(ddMon.DeviceID, wxConvUTF8);
960 DeviceID = DeviceID.Mid(8);
961 DeviceID = DeviceID.Mid(0, DeviceID.Find('\\'));
962
963 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
964 }
965 devMon++;
966
967 ZeroMemory(&ddMon, sizeof(ddMon));
968 ddMon.cb = sizeof(ddMon);
969 }
970
971 ZeroMemory(&dd, sizeof(dd));
972 dd.cb = sizeof(dd);
973 dev++;
974 }
975 m_monitorWidth = WidthMm;
976 m_monitorHeight = HeightMm;
977 }
978
979 if (width) *width = m_monitorWidth;
980 if (height) *height = m_monitorHeight;
981
982 return bFoundDevice;
983}
984
985#endif // __WXMSW__
986
987int BasePlatform::GetSvgStdIconSize(const wxWindow* w, bool touch) {
988 double size = w->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
989#if wxCHECK_VERSION(3, 1, 2)
990 // Apply scale factor, mostly for Windows. Other platforms
991 // does this in the toolkits, ToDIP() is aware of this.
992 size *= static_cast<double>(w->ToDIP(100)) / 100.;
993#endif
994 // Force minimum physical size for touch screens
995 if (touch) {
996 double pixel_per_mm = wxGetDisplaySize().x / GetDisplaySizeMM();
997 size = std::max(size, 7.0 * pixel_per_mm);
998 }
999 return std::round(size);
1000}
1001
1002bool platform::GetMemoryStatus(int* mem_total, int* mem_used) {
1003#ifdef __ANDROID__
1004 return androidGetMemoryStatus(mem_total, mem_used);
1005#endif
1006
1007#if defined(__linux__)
1008 // Use sysinfo to obtain total RAM
1009 if (mem_total) {
1010 *mem_total = 0;
1011 struct sysinfo sys_info;
1012 if (sysinfo(&sys_info) != -1)
1013 *mem_total = ((uint64_t)sys_info.totalram * sys_info.mem_unit) / 1024;
1014 }
1015 // Use filesystem /proc/self/statm to determine memory status
1016 // Provides information about memory usage, measured in pages. The columns
1017 // are: size total program size (same as VmSize in /proc/[pid]/status)
1018 // resident resident set size (same as VmRSS in /proc/[pid]/status)
1019 // share shared pages (from shared mappings)
1020 // text text (code)
1021 // lib library (unused in Linux 2.6)
1022 // data data + stack
1023 // dt dirty pages (unused in Linux 2.6)
1024
1025 if (mem_used) {
1026 *mem_used = 0;
1027 FILE* file = fopen("/proc/self/statm", "r");
1028 if (file) {
1029 if (fscanf(file, "%d", mem_used) != 1) {
1030 wxLogWarning("Cannot parse /proc/self/statm (!)");
1031 }
1032 *mem_used *= 4; // XXX assume 4K page
1033 fclose(file);
1034 }
1035 }
1036
1037 return true;
1038
1039#endif /* __linux__ */
1040
1041#ifdef __WXMSW__
1042 HANDLE hProcess;
1043 PROCESS_MEMORY_COUNTERS pmc;
1044
1045 unsigned long processID = wxGetProcessId();
1046
1047 if (mem_used) {
1048 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE,
1049 processID);
1050
1051 if (hProcess && GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
1052 /*
1053 printf( "\tPageFaultCount: 0x%08X\n", pmc.PageFaultCount );
1054 printf( "\tPeakWorkingSetSize: 0x%08X\n",
1055 pmc.PeakWorkingSetSize );
1056 printf( "\tWorkingSetSize: 0x%08X\n", pmc.WorkingSetSize );
1057 printf( "\tQuotaPeakPagedPoolUsage: 0x%08X\n",
1058 pmc.QuotaPeakPagedPoolUsage );
1059 printf( "\tQuotaPagedPoolUsage: 0x%08X\n",
1060 pmc.QuotaPagedPoolUsage );
1061 printf( "\tQuotaPeakNonPagedPoolUsage: 0x%08X\n",
1062 pmc.QuotaPeakNonPagedPoolUsage );
1063 printf( "\tQuotaNonPagedPoolUsage: 0x%08X\n",
1064 pmc.QuotaNonPagedPoolUsage );
1065 printf( "\tPagefileUsage: 0x%08X\n", pmc.PagefileUsage );
1066 printf( "\tPeakPagefileUsage: 0x%08X\n",
1067 pmc.PeakPagefileUsage );
1068 */
1069 *mem_used = pmc.WorkingSetSize / 1024;
1070 }
1071
1072 CloseHandle(hProcess);
1073 }
1074
1075 if (mem_total) {
1076 MEMORYSTATUSEX statex;
1077
1078 statex.dwLength = sizeof(statex);
1079
1080 GlobalMemoryStatusEx(&statex);
1081 /*
1082 _tprintf (TEXT("There is %*ld percent of memory in use.\n"),
1083 WIDTH, statex.dwMemoryLoad);
1084 _tprintf (TEXT("There are %*I64d total Kbytes of physical memory.\n"),
1085 WIDTH, statex.ullTotalPhys/DIV);
1086 _tprintf (TEXT("There are %*I64d free Kbytes of physical memory.\n"),
1087 WIDTH, statex.ullAvailPhys/DIV);
1088 _tprintf (TEXT("There are %*I64d total Kbytes of paging file.\n"),
1089 WIDTH, statex.ullTotalPageFile/DIV);
1090 _tprintf (TEXT("There are %*I64d free Kbytes of paging file.\n"),
1091 WIDTH, statex.ullAvailPageFile/DIV);
1092 _tprintf (TEXT("There are %*I64d total Kbytes of virtual memory.\n"),
1093 WIDTH, statex.ullTotalVirtual/DIV);
1094 _tprintf (TEXT("There are %*I64d free Kbytes of virtual memory.\n"),
1095 WIDTH, statex.ullAvailVirtual/DIV);
1096 */
1097
1098 *mem_total = statex.ullTotalPhys / 1024;
1099 }
1100 return true;
1101#endif
1102
1103#ifdef __WXMAC__
1104
1105 if (g_tick != g_lastMemTick) {
1106 malloc_zone_pressure_relief(NULL, 0);
1107
1108 int bytesInUse = 0;
1109 int blocksInUse = 0;
1110 int sizeAllocated = 0;
1111
1112 malloc_statistics_t stats;
1113 stats.blocks_in_use = 0;
1114 stats.size_in_use = 0;
1115 stats.max_size_in_use = 0;
1116 stats.size_allocated = 0;
1117 malloc_zone_statistics(NULL, &stats);
1118 bytesInUse += stats.size_in_use;
1119 blocksInUse += stats.blocks_in_use;
1120 sizeAllocated += stats.size_allocated;
1121
1122 g_memUsed = sizeAllocated >> 10;
1123
1124 // printf("mem_used (Mb): %d %d \n", g_tick, g_memUsed / 1024);
1125 g_lastMemTick = g_tick;
1126 }
1127
1128 if (mem_used) *mem_used = g_memUsed;
1129 if (mem_total) {
1130 *mem_total = 4000;
1131 FILE* fpIn = popen("sysctl -n hw.memsize", "r");
1132 if (fpIn) {
1133 double pagesUsed = 0.0, totalPages = 0.0;
1134 char buf[64];
1135 if (fgets(buf, sizeof(buf), fpIn) != NULL) {
1136 *mem_total = atol(buf) >> 10;
1137 }
1138 }
1139 }
1140
1141 return true;
1142#endif
1143
1144 if (mem_used) *mem_used = 0;
1145 if (mem_total) *mem_total = 0;
1146 return false;
1147}
wxString GetPluginDataDir(const char *plugin_name)
Returns an installed plugin's data directory given a plugin name.
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Basic platform specific support utilities without GUI deps.
bool GetMemoryStatus(int *mem_total, int *mem_used)
Return total system RAM and size of program Values returned are in kilobytes.
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.
wxString GetSupplementalLicenseString()
Android license details, otherwise "".
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:87
Global variables reflecting command line options and arguments.
wxString g_winPluginDir
Base plugin directory on Windows.
Global variables stored in configuration file.
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Enhanced logging interface on top of wx/log.h.
MacOS hardware probing functions.
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.