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 m_PrivateDataDir = std_path.GetUserDataDir(); // should be ~/.opencpn
359#endif
360
361 if (g_bportable) m_PrivateDataDir = GetHomeDir();
362 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
363 m_PrivateDataDir.RemoveLast();
364
365#ifdef __ANDROID__
366 m_PrivateDataDir = androidGetPrivateDir();
367#endif
368 }
369 return m_PrivateDataDir;
370}
371
373 if (g_winPluginDir != "") {
374 wxLogMessage("winPluginDir: Using value from ini file.");
375 wxFileName fn(g_winPluginDir);
376 if (!fn.DirExists()) {
377 wxLogWarning("Plugin dir %s does not exist",
378 fn.GetFullPath().mb_str().data());
379 }
380 fn.Normalize();
381 return fn.GetFullPath();
382 }
383 wxString winPluginDir;
384 // Portable case: plugins directory is in the .exe folder
385 if (g_bportable) {
386 winPluginDir = (GetHomeDir() + "plugins");
387 if (ocpn::exists(winPluginDir.ToStdString())) {
388 wxLogMessage("Using portable plugin dir: %s", winPluginDir);
389 return winPluginDir;
390 }
391 }
392 // Standard case: c:\Users\%USERPROFILE%\AppData\Local
393 bool ok = wxGetEnv("LOCALAPPDATA", &winPluginDir);
394 if (!ok) {
395 wxLogMessage("winPluginDir: Cannot lookup LOCALAPPDATA");
396 // Without %LOCALAPPDATA%: Use default location if it exists.
397 std::string path(wxGetHomeDir().ToStdString());
398 path += "\\AppData\\Local";
399 if (ocpn::exists(path)) {
400 winPluginDir = wxString(path.c_str());
401 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
402 ok = true;
403 }
404 }
405 if (!ok) {
406 // Usually: c:\Users\%USERPROFILE%\AppData\Roaming
407 ok = wxGetEnv("APPDATA", &winPluginDir);
408 }
409 if (!ok) {
410 // Without %APPDATA%: Use default location if it exists.
411 wxLogMessage("winPluginDir: Cannot lookup APPDATA");
412 std::string path(wxGetHomeDir().ToStdString());
413 path += "\\AppData\\Roaming";
414 if (ocpn::exists(path)) {
415 winPluginDir = wxString(path.c_str());
416 ok = true;
417 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
418 }
419 }
420 if (!ok) {
421 // {Documents and Settings}\.. on W7, else \ProgramData
422 winPluginDir = GetHomeDir();
423 }
424 wxFileName path(winPluginDir);
425 path.Normalize();
426 winPluginDir = path.GetFullPath() + "\\opencpn\\plugins";
427 wxLogMessage("Using private plugin dir: %s", winPluginDir);
428 return winPluginDir;
429}
430
432 if (m_PluginsDir.IsEmpty()) {
433 wxStandardPaths& std_path = GetStdPaths();
434
435 // Get the PlugIns directory location
436 m_PluginsDir = std_path.GetPluginsDir(); // linux: {prefix}/lib/opencpn
437 // Mac: appname.app/Contents/PlugIns
438#ifdef __WXMSW__
439 m_PluginsDir += "\\plugins"; // Windows: {exe dir}/plugins
440#endif
441 if (g_bportable) {
442 m_PluginsDir = GetHomeDir();
443 m_PluginsDir += "plugins";
444 }
445
446#ifdef __ANDROID__
447 // something like: data/data/org.opencpn.opencpn
448 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
449 fdir.RemoveLastDir();
450 m_PluginsDir = fdir.GetPath();
451#endif
452 }
453 return m_PluginsDir;
454}
455
456wxString* AbstractPlatform::GetPluginDirPtr() {
457 if (m_PluginsDir.IsEmpty()) GetPluginDir();
458 return &m_PluginsDir;
459}
460
461bool AbstractPlatform::isPlatformCapable(int flag) {
462#ifndef __ANDROID__
463 return true;
464#else
465 if (flag == PLATFORM_CAP_PLUGINS) {
466 long platver;
467 wxString tsdk(android_plat_spc.msdk);
468 if (tsdk.ToLong(&platver)) {
469 if (platver >= 11) return true;
470 }
471 } else if (flag == PLATFORM_CAP_FASTPAN) {
472 long platver;
473 wxString tsdk(android_plat_spc.msdk);
474 if (tsdk.ToLong(&platver)) {
475 if (platver >= 14) return true;
476 }
477 }
478
479 return false;
480#endif
481}
482
483void appendOSDirSlash(wxString* pString) {
484 wxChar sep = wxFileName::GetPathSeparator();
485 if (pString->Last() != sep) pString->Append(sep);
486}
487
488wxString AbstractPlatform::GetWritableDocumentsDir() {
489 wxString dir;
490
491#ifdef __ANDROID__
492 dir = androidGetExtStorageDir(); // Used for Chart storage, typically
493#else
494 wxStandardPaths& std_path = GetStdPaths();
495 dir = std_path.GetDocumentsDir();
496#endif
497 return dir;
498}
499
500bool AbstractPlatform::DetectOSDetail(OCPN_OSDetail* detail) {
501 if (!detail) return false;
502
503 // We take some defaults from build-time definitions
504 detail->osd_name = std::string(PKG_TARGET);
505 detail->osd_version = std::string(PKG_TARGET_VERSION);
506
507 // Now parse by basic platform
508#ifdef __linux__
509 if (wxFileExists("/etc/os-release")) {
510 wxTextFile release_file("/etc/os-release");
511 if (release_file.Open()) {
512 wxString val;
513 for (wxString str = release_file.GetFirstLine(); !release_file.Eof();
514 str = release_file.GetNextLine()) {
515 if (str.StartsWith("NAME")) {
516 val = str.AfterFirst('=').Mid(1);
517 val = val.Mid(0, val.Length() - 1);
518 if (val.Length()) detail->osd_name = std::string(val.mb_str());
519 } else if (str.StartsWith("VERSION_ID")) {
520 val = str.AfterFirst('=').Mid(1);
521 val = val.Mid(0, val.Length() - 1);
522 if (val.Length()) detail->osd_version = std::string(val.mb_str());
523 } else if (str.StartsWith("ID=")) {
524 val = str.AfterFirst('=');
525 if (val.Length()) detail->osd_ID = ocpn::split(val.mb_str(), " ")[0];
526 } else if (str.StartsWith("ID_LIKE")) {
527 if (val.StartsWith('"')) {
528 val = str.AfterFirst('=').Mid(1);
529 val = val.Mid(0, val.Length() - 1);
530 } else {
531 val = str.AfterFirst('=');
532 }
533
534 if (val.Length()) {
535 detail->osd_names_like = ocpn::split(val.mb_str(), " ");
536 }
537 }
538 }
539
540 release_file.Close();
541 }
542 if (detail->osd_name == "Linux Mint") {
543 if (wxFileExists("/etc/upstream-release/lsb-release")) {
544 wxTextFile upstream_release_file("/etc/upstream-release/lsb-release");
545 if (upstream_release_file.Open()) {
546 wxString val;
547 for (wxString str = upstream_release_file.GetFirstLine();
548 !upstream_release_file.Eof();
549 str = upstream_release_file.GetNextLine()) {
550 if (str.StartsWith("DISTRIB_RELEASE")) {
551 val = str.AfterFirst('=').Mid(0);
552 val = val.Mid(0, val.Length());
553 if (val.Length()) detail->osd_version = std::string(val.mb_str());
554 }
555 }
556 upstream_release_file.Close();
557 }
558 }
559 }
560 }
561#endif
562
563 // Set the default processor architecture
564 detail->osd_arch = std::string("x86_64");
565
566 // then see what is actually running.
567 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
568 wxArchitecture arch = platformInfo.GetArchitecture();
569 if (arch == wxARCH_32) detail->osd_arch = std::string("i386");
570
571#ifdef ocpnARM
572 // arm supports a multiarch runtime environment
573 // That is, the OS may be 64 bit, but OCPN may be built as a 32 bit binary
574 // So, we cannot trust the wxPlatformInfo architecture determination.
575 detail->osd_arch = std::string("arm64");
576#ifdef ocpnARMHF
577 detail->osd_arch = std::string("armhf");
578#endif
579#endif
580
581#ifdef __ANDROID__
582 detail->osd_arch = std::string("arm64");
583 if (arch == wxARCH_32) detail->osd_arch = std::string("armhf");
584#endif
585
586#ifdef __WXOSX__
587 if (IsAppleSilicon() == 1) {
588 if (ProcessIsTranslated() != 1) {
589 detail->osd_arch = std::string("arm64");
590 } else {
591 detail->osd_arch = std::string("x86_64");
592 }
593 } else {
594 detail->osd_arch = std::string("x86_64");
595 }
596#endif
597
598 return true;
599}
600
601wxString& AbstractPlatform::GetConfigFileName() {
602 if (m_config_file_name.IsEmpty()) {
603 // Establish the location of the config file
604 wxStandardPaths& std_path = GetStdPaths();
605
606#ifdef __WXMSW__
607 m_config_file_name = "opencpn.ini";
608 m_config_file_name.Prepend(GetHomeDir());
609
610#elif defined __WXOSX__
611 m_config_file_name =
612 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
613 appendOSDirSlash(&m_config_file_name);
614 m_config_file_name.Append("opencpn");
615 appendOSDirSlash(&m_config_file_name);
616 m_config_file_name.Append("opencpn.ini");
617#elif defined FLATPAK
618 m_config_file_name = GetPrivateDataDir();
619 m_config_file_name.Append("/opencpn.conf");
620 // Usually ~/.var/app/org.opencpn.OpenCPN/config/opencpn.conf
621#else
622 m_config_file_name = std_path.GetUserDataDir(); // should be ~/.opencpn
623 appendOSDirSlash(&m_config_file_name);
624 m_config_file_name.Append("opencpn.conf");
625#endif
626
627 if (g_bportable) {
628 m_config_file_name = GetHomeDir();
629#ifdef __WXMSW__
630 m_config_file_name += "opencpn.ini";
631#elif defined __WXOSX__
632 m_config_file_name += "opencpn.ini";
633#else
634 m_config_file_name += "opencpn.conf";
635#endif
636 }
637
638#ifdef __ANDROID__
639 m_config_file_name = androidGetPrivateDir();
640 appendOSDirSlash(&m_config_file_name);
641 m_config_file_name += "opencpn.conf";
642#endif
643 if (!g_configdir.empty()) {
644 m_config_file_name = g_configdir;
645 m_config_file_name.Append("/opencpn.conf");
646 }
647 }
648 return m_config_file_name;
649}
650
651bool BasePlatform::InitializeLogFile() {
652 // Establish Log File location
653 mlog_file = GetPrivateDataDir();
654 appendOSDirSlash(&mlog_file);
655
656#ifdef __WXOSX__
657
658 wxFileName LibPref(mlog_file); // starts like "~/Library/Preferences/opencpn"
659 LibPref.RemoveLastDir(); // takes off "opencpn"
660 LibPref.RemoveLastDir(); // takes off "Preferences"
661
662 mlog_file = LibPref.GetFullPath();
663 appendOSDirSlash(&mlog_file);
664
665 mlog_file.Append("Logs/"); // so, on OS X, opencpn.log ends up in
666 // ~/Library/Logs which makes it accessible to
667 // Applications/Utilities/Console....
668#endif
669
670 // create the opencpn "home" directory if we need to
671 wxFileName wxHomeFiledir(GetHomeDir());
672 if (true != wxHomeFiledir.DirExists(wxHomeFiledir.GetPath()))
673 if (!wxHomeFiledir.Mkdir(wxHomeFiledir.GetPath())) {
674 wxASSERT_MSG(false, "Cannot create opencpn home directory");
675 return false;
676 }
677
678 // create the opencpn "log" directory if we need to
679 wxFileName wxLogFiledir(mlog_file);
680 if (true != wxLogFiledir.DirExists(wxLogFiledir.GetPath())) {
681 if (!wxLogFiledir.Mkdir(wxLogFiledir.GetPath())) {
682 wxASSERT_MSG(false, "Cannot create opencpn log directory");
683 return false;
684 }
685 }
686
687 mlog_file.Append("opencpn.log");
688 wxString logit = mlog_file;
689
690#ifdef __ANDROID__
691 wxCharBuffer abuf = mlog_file.ToUTF8();
692 qDebug() << "logfile " << abuf.data();
693#endif
694
695 // Constrain the size of the log file
696 if (::wxFileExists(mlog_file)) {
697 if (wxFileName::GetSize(mlog_file) > 1000000) {
698 wxString oldlog = mlog_file;
699 oldlog.Append(".log");
700 // Defer the showing of this messagebox until the system locale is
701 // established.
702 large_log_message = ("Old log will be moved to opencpn.log.log");
703 ::wxRenameFile(mlog_file, oldlog);
704 }
705 }
706#ifdef __ANDROID__
707 if (::wxFileExists(mlog_file)) {
708 // Force new logfile for each instance
709 // TODO Remove this behaviour on Release
710 ::wxRemoveFile(mlog_file);
711 }
712#endif
713
714 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
715
716 auto logger = new OcpnLog(mlog_file.mb_str());
717 if (m_old_logger) {
718 delete m_old_logger;
719 }
720 m_old_logger = wxLog::SetActiveTarget(logger);
721
722 return true;
723}
724
725void AbstractPlatform::CloseLogFile() {
726 if (m_old_logger) {
727 delete wxLog::SetActiveTarget(m_old_logger);
728 m_old_logger = nullptr;
729 }
730}
731
733 if (g_bportable) {
734 wxString sep = wxFileName::GetPathSeparator();
735 wxString ret = GetPrivateDataDir() + sep + "plugins";
736 return ret;
737 }
738
739 if (m_pluginDataPath != "") {
740 return m_pluginDataPath;
741 }
742 wxString dirs("");
743#ifdef __ANDROID__
744 wxString pluginDir = GetPrivateDataDir() + "/plugins";
745 dirs += pluginDir;
746#else
747 auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
748 if (isFlatpacked()) {
749 dirs = "~/.var/app/org.opencpn.OpenCPN/data/opencpn/plugins";
750 } else if (osSystemId & wxOS_UNIX_LINUX) {
751 dirs = GetLinuxDataPath();
752 } else if (osSystemId & wxOS_WINDOWS) {
753 dirs = GetWinPluginBaseDir();
754 } else if (osSystemId & wxOS_MAC) {
755 dirs = "/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
756 dirs +=
757 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
758 }
759#endif
760
761 m_pluginDataPath = ExpandPaths(dirs, this);
762 if (m_pluginDataPath != "") {
763 m_pluginDataPath += ";";
764 }
765 m_pluginDataPath += GetPluginDir();
766 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
767 m_pluginDataPath.RemoveLast();
768 }
769 wxLogMessage("Using plugin data path: %s", m_pluginDataPath.mb_str().data());
770 return m_pluginDataPath;
771}
772
773#ifdef __ANDROID__
774void AbstractPlatform::ShowBusySpinner() {
775 if (!m_isBusy) {
776 androidShowBusyIcon();
777 m_isBusy = true;
778 }
779}
780#else
781void AbstractPlatform::ShowBusySpinner() {
782 if (!m_isBusy) {
783 ::wxBeginBusyCursor();
784 m_isBusy = true;
785 }
786}
787#endif
788
789#ifdef __ANDROID__
790void AbstractPlatform::HideBusySpinner() {
791 if (m_isBusy) {
792 androidHideBusyIcon();
793 m_isBusy = false;
794 }
795}
796#else
797void AbstractPlatform::HideBusySpinner() {
798 if (m_isBusy) {
799 ::wxEndBusyCursor();
800 m_isBusy = false;
801 }
802}
803#endif
804
805// getDisplaySize
806
807#if defined(__ANDROID__)
808wxSize BasePlatform::getDisplaySize() { return getAndroidDisplayDimensions(); }
809
810#else
811wxSize BasePlatform::getDisplaySize() { return wxSize(0, 0); }
812#endif
813
814// GetDisplaySizeMM
815double BasePlatform::GetDisplaySizeMM() {
816 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
817 return m_displaySizeMMOverride[0];
818 }
819 double ret = 0;
820
821#ifdef __ANDROID__
822 ret = GetAndroidDisplaySize();
823#endif
824
825 wxLogDebug("Detected display size (horizontal): %d mm", (int)ret);
826 return ret;
827}
828
829#if defined(__ANDROID__)
830double BasePlatform::GetDisplayDPmm() { return getAndroidDPmm(); }
831
832#else
833double BasePlatform::GetDisplayDPmm() {
834 if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
835 double r = getDisplaySize().x; // dots
836 return r / GetDisplaySizeMM();
837 } else {
838 // This is a console app... assuming 300 DPI ~ 12 DPmm
839 return 12.0;
840 }
841}
842#endif
843
845 double rv = 1.0;
846#ifdef __WXMSW__
847 if (win) rv = (double)(win->ToDIP(100)) / 100.;
848#endif
849 return rv;
850}
851
852unsigned int AbstractPlatform::GetSelectRadiusPix() {
853 return GetDisplayDPmm() *
854 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
855}
856
857#ifdef __WXMSW__
858
859#define NAME_SIZE 128
860
861const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
862 0x00, 0x2b, 0xe1, 0x03, 0x18};
863
864// Assumes hDevRegKey is valid
865bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, int* WidthMm,
866 int* HeightMm) {
867 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
868 TCHAR valueName[NAME_SIZE];
869
870 BYTE EDIDdata[1024];
871 DWORD edidsize = sizeof(EDIDdata);
872
873 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
874 ++i) {
875 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
876 &AcutalValueNameLength, NULL, &dwType,
877 EDIDdata, // buffer
878 &edidsize); // buffer size
879
880 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, L"EDID")) continue;
881
882 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
883 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
884
885 return true; // valid EDID found
886 }
887
888 return false; // EDID not found
889}
890
891bool GetSizeForDevID(wxString& TargetDevID, int* WidthMm, int* HeightMm) {
892 HDEVINFO devInfo =
893 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR, // class GUID
894 NULL, // enumerator
895 NULL, // HWND
896 DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
897 NULL, // device info, create a new one.
898 NULL, // machine name, local machine
899 NULL); // reserved
900
901 if (NULL == devInfo) return false;
902
903 bool bRes = false;
904
905 for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
906 SP_DEVINFO_DATA devInfoData;
907 memset(&devInfoData, 0, sizeof(devInfoData));
908 devInfoData.cbSize = sizeof(devInfoData);
909
910 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
911 wchar_t Instance[80];
912 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
913 NULL);
914 wxString instance(Instance);
915 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND) continue;
916
917 HKEY hDevRegKey = SetupDiOpenDevRegKey(
918 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
919
920 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) continue;
921
922 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
923
924 RegCloseKey(hDevRegKey);
925 }
926 }
927 SetupDiDestroyDeviceInfoList(devInfo);
928 return bRes;
929}
930
931bool AbstractPlatform::GetWindowsMonitorSize(int* width, int* height) {
932 bool bFoundDevice = true;
933
934 if (m_monitorWidth < 10) {
935 int WidthMm = 0;
936 int HeightMm = 0;
937
938 DISPLAY_DEVICE dd;
939 dd.cb = sizeof(dd);
940 DWORD dev = 0; // device index
941 int id = 1; // monitor number, as used by Display Properties > Settings
942
943 wxString DeviceID;
944 bFoundDevice = false;
945 while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice) {
946 DISPLAY_DEVICE ddMon;
947 ZeroMemory(&ddMon, sizeof(ddMon));
948 ddMon.cb = sizeof(ddMon);
949 DWORD devMon = 0;
950
951 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
952 !bFoundDevice) {
953 if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
954 !(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
955 DeviceID = wxString(ddMon.DeviceID, wxConvUTF8);
956 DeviceID = DeviceID.Mid(8);
957 DeviceID = DeviceID.Mid(0, DeviceID.Find('\\'));
958
959 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
960 }
961 devMon++;
962
963 ZeroMemory(&ddMon, sizeof(ddMon));
964 ddMon.cb = sizeof(ddMon);
965 }
966
967 ZeroMemory(&dd, sizeof(dd));
968 dd.cb = sizeof(dd);
969 dev++;
970 }
971 m_monitorWidth = WidthMm;
972 m_monitorHeight = HeightMm;
973 }
974
975 if (width) *width = m_monitorWidth;
976 if (height) *height = m_monitorHeight;
977
978 return bFoundDevice;
979}
980
981#endif // __WXMSW__
982
983int BasePlatform::GetSvgStdIconSize(const wxWindow* w, bool touch) {
984 double size = w->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
985#if wxCHECK_VERSION(3, 1, 2)
986 // Apply scale factor, mostly for Windows. Other platforms
987 // does this in the toolkits, ToDIP() is aware of this.
988 size *= static_cast<double>(w->ToDIP(100)) / 100.;
989#endif
990 // Force minimum physical size for touch screens
991 if (touch) {
992 double pixel_per_mm = wxGetDisplaySize().x / GetDisplaySizeMM();
993 size = std::max(size, 7.0 * pixel_per_mm);
994 }
995 return std::round(size);
996}
997
998bool platform::GetMemoryStatus(int* mem_total, int* mem_used) {
999#ifdef __ANDROID__
1000 return androidGetMemoryStatus(mem_total, mem_used);
1001#endif
1002
1003#if defined(__linux__)
1004 // Use sysinfo to obtain total RAM
1005 if (mem_total) {
1006 *mem_total = 0;
1007 struct sysinfo sys_info;
1008 if (sysinfo(&sys_info) != -1)
1009 *mem_total = ((uint64_t)sys_info.totalram * sys_info.mem_unit) / 1024;
1010 }
1011 // Use filesystem /proc/self/statm to determine memory status
1012 // Provides information about memory usage, measured in pages. The columns
1013 // are: size total program size (same as VmSize in /proc/[pid]/status)
1014 // resident resident set size (same as VmRSS in /proc/[pid]/status)
1015 // share shared pages (from shared mappings)
1016 // text text (code)
1017 // lib library (unused in Linux 2.6)
1018 // data data + stack
1019 // dt dirty pages (unused in Linux 2.6)
1020
1021 if (mem_used) {
1022 *mem_used = 0;
1023 FILE* file = fopen("/proc/self/statm", "r");
1024 if (file) {
1025 if (fscanf(file, "%d", mem_used) != 1) {
1026 wxLogWarning("Cannot parse /proc/self/statm (!)");
1027 }
1028 *mem_used *= 4; // XXX assume 4K page
1029 fclose(file);
1030 }
1031 }
1032
1033 return true;
1034
1035#endif /* __linux__ */
1036
1037#ifdef __WXMSW__
1038 HANDLE hProcess;
1039 PROCESS_MEMORY_COUNTERS pmc;
1040
1041 unsigned long processID = wxGetProcessId();
1042
1043 if (mem_used) {
1044 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE,
1045 processID);
1046
1047 if (hProcess && GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
1048 /*
1049 printf( "\tPageFaultCount: 0x%08X\n", pmc.PageFaultCount );
1050 printf( "\tPeakWorkingSetSize: 0x%08X\n",
1051 pmc.PeakWorkingSetSize );
1052 printf( "\tWorkingSetSize: 0x%08X\n", pmc.WorkingSetSize );
1053 printf( "\tQuotaPeakPagedPoolUsage: 0x%08X\n",
1054 pmc.QuotaPeakPagedPoolUsage );
1055 printf( "\tQuotaPagedPoolUsage: 0x%08X\n",
1056 pmc.QuotaPagedPoolUsage );
1057 printf( "\tQuotaPeakNonPagedPoolUsage: 0x%08X\n",
1058 pmc.QuotaPeakNonPagedPoolUsage );
1059 printf( "\tQuotaNonPagedPoolUsage: 0x%08X\n",
1060 pmc.QuotaNonPagedPoolUsage );
1061 printf( "\tPagefileUsage: 0x%08X\n", pmc.PagefileUsage );
1062 printf( "\tPeakPagefileUsage: 0x%08X\n",
1063 pmc.PeakPagefileUsage );
1064 */
1065 *mem_used = pmc.WorkingSetSize / 1024;
1066 }
1067
1068 CloseHandle(hProcess);
1069 }
1070
1071 if (mem_total) {
1072 MEMORYSTATUSEX statex;
1073
1074 statex.dwLength = sizeof(statex);
1075
1076 GlobalMemoryStatusEx(&statex);
1077 /*
1078 _tprintf (TEXT("There is %*ld percent of memory in use.\n"),
1079 WIDTH, statex.dwMemoryLoad);
1080 _tprintf (TEXT("There are %*I64d total Kbytes of physical memory.\n"),
1081 WIDTH, statex.ullTotalPhys/DIV);
1082 _tprintf (TEXT("There are %*I64d free Kbytes of physical memory.\n"),
1083 WIDTH, statex.ullAvailPhys/DIV);
1084 _tprintf (TEXT("There are %*I64d total Kbytes of paging file.\n"),
1085 WIDTH, statex.ullTotalPageFile/DIV);
1086 _tprintf (TEXT("There are %*I64d free Kbytes of paging file.\n"),
1087 WIDTH, statex.ullAvailPageFile/DIV);
1088 _tprintf (TEXT("There are %*I64d total Kbytes of virtual memory.\n"),
1089 WIDTH, statex.ullTotalVirtual/DIV);
1090 _tprintf (TEXT("There are %*I64d free Kbytes of virtual memory.\n"),
1091 WIDTH, statex.ullAvailVirtual/DIV);
1092 */
1093
1094 *mem_total = statex.ullTotalPhys / 1024;
1095 }
1096 return true;
1097#endif
1098
1099#ifdef __WXMAC__
1100
1101 if (g_tick != g_lastMemTick) {
1102 malloc_zone_pressure_relief(NULL, 0);
1103
1104 int bytesInUse = 0;
1105 int blocksInUse = 0;
1106 int sizeAllocated = 0;
1107
1108 malloc_statistics_t stats;
1109 stats.blocks_in_use = 0;
1110 stats.size_in_use = 0;
1111 stats.max_size_in_use = 0;
1112 stats.size_allocated = 0;
1113 malloc_zone_statistics(NULL, &stats);
1114 bytesInUse += stats.size_in_use;
1115 blocksInUse += stats.blocks_in_use;
1116 sizeAllocated += stats.size_allocated;
1117
1118 g_memUsed = sizeAllocated >> 10;
1119
1120 // printf("mem_used (Mb): %d %d \n", g_tick, g_memUsed / 1024);
1121 g_lastMemTick = g_tick;
1122 }
1123
1124 if (mem_used) *mem_used = g_memUsed;
1125 if (mem_total) {
1126 *mem_total = 4000;
1127 FILE* fpIn = popen("sysctl -n hw.memsize", "r");
1128 if (fpIn) {
1129 double pagesUsed = 0.0, totalPages = 0.0;
1130 char buf[64];
1131 if (fgets(buf, sizeof(buf), fpIn) != NULL) {
1132 *mem_total = atol(buf) >> 10;
1133 }
1134 }
1135 }
1136
1137 return true;
1138#endif
1139
1140 if (mem_used) *mem_used = 0;
1141 if (mem_total) *mem_total = 0;
1142 return false;
1143}
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.