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
275 wxString lic;
276#ifdef __ANDROID__
277 lic = androidGetSupplementalLicense();
278#endif
279 return lic;
280}
281
282wxString GetPluginDataDir(const char* plugin_name) {
283 static const wxString sep = wxFileName::GetPathSeparator();
284
285 wxString datadirs = g_BasePlatform->GetPluginDataPath();
286 wxLogMessage(_T("PlugInManager: Using data dirs from: ") + datadirs);
287 wxStringTokenizer dirs(datadirs, ";");
288 while (dirs.HasMoreTokens()) {
289 wxString dir = dirs.GetNextToken();
290 wxFileName tryDirName(dir);
291 wxDir tryDir;
292 if (!tryDir.Open(tryDirName.GetFullPath())) continue;
293 wxString next;
294 bool more = tryDir.GetFirst(&next);
295 while (more) {
296 if (next == plugin_name) {
297 next = next.Prepend(tryDirName.GetFullPath() + sep);
298 wxLogMessage(_T("PlugInManager: using data dir: %s"), next);
299 return next;
300 }
301 more = tryDir.GetNext(&next);
302 }
303 tryDir.Close();
304 }
305 wxLogMessage(_T("Warning: no data directory found, using \"\""));
306 return "";
307}
308
310 if (!m_PrivateDataDir.IsEmpty() && g_configdir.empty())
311 return m_PrivateDataDir;
312 if (!g_configdir.empty()) {
313 wxString path = g_configdir;
314 if (path.Last() == wxFileName::GetPathSeparator()) path.RemoveLast();
315 m_default_private_datadir = path;
316 return m_default_private_datadir; // FIXME (leamas) normalize and trust
317 // g_configdir
318 }
319 return DefaultPrivateDataDir();
320}
321
323 if (m_PrivateDataDir.IsEmpty()) {
324 // Establish the prefix of the location of user specific data files
325 wxStandardPaths& std_path = GetStdPaths();
326
327#ifdef __WXMSW__
328 m_PrivateDataDir =
329 GetHomeDir(); // should be {Documents and Settings}\......
330#elif defined FLATPAK
331 std::string config_home;
332 if (getenv("XDG_CONFIG_HOME")) {
333 config_home = getenv("XDG_CONFIG_HOME");
334 } else {
335 config_home = getenv("HOME");
336 config_home += "/.var/app/org.opencpn.OpenCPN/config";
337 }
338 m_PrivateDataDir = config_home + "/opencpn";
339
340#elif defined __WXOSX__
341 m_PrivateDataDir =
342 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
343 appendOSDirSlash(&m_PrivateDataDir);
344 m_PrivateDataDir.Append(_T("opencpn"));
345#else
346 m_PrivateDataDir = std_path.GetUserDataDir(); // should be ~/.opencpn
347#endif
348
349 if (g_bportable) m_PrivateDataDir = GetHomeDir();
350 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
351 m_PrivateDataDir.RemoveLast();
352
353#ifdef __ANDROID__
354 m_PrivateDataDir = androidGetPrivateDir();
355#endif
356 }
357 return m_PrivateDataDir;
358}
359
361 if (g_winPluginDir != "") {
362 wxLogMessage("winPluginDir: Using value from ini file.");
363 wxFileName fn(g_winPluginDir);
364 if (!fn.DirExists()) {
365 wxLogWarning("Plugin dir %s does not exist",
366 fn.GetFullPath().mb_str().data());
367 }
368 fn.Normalize();
369 return fn.GetFullPath();
370 }
371 wxString winPluginDir;
372 // Portable case: plugins directory is in the .exe folder
373 if (g_bportable) {
374 winPluginDir = (GetHomeDir() + _T("plugins"));
375 if (ocpn::exists(winPluginDir.ToStdString())) {
376 wxLogMessage("Using portable plugin dir: %s", winPluginDir);
377 return winPluginDir;
378 }
379 }
380 // Standard case: c:\Users\%USERPROFILE%\AppData\Local
381 bool ok = wxGetEnv(_T("LOCALAPPDATA"), &winPluginDir);
382 if (!ok) {
383 wxLogMessage("winPluginDir: Cannot lookup LOCALAPPDATA");
384 // Without %LOCALAPPDATA%: Use default location if it exists.
385 std::string path(wxGetHomeDir().ToStdString());
386 path += "\\AppData\\Local";
387 if (ocpn::exists(path)) {
388 winPluginDir = wxString(path.c_str());
389 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
390 ok = true;
391 }
392 }
393 if (!ok) {
394 // Usually: c:\Users\%USERPROFILE%\AppData\Roaming
395 ok = wxGetEnv(_T("APPDATA"), &winPluginDir);
396 }
397 if (!ok) {
398 // Without %APPDATA%: Use default location if it exists.
399 wxLogMessage("winPluginDir: Cannot lookup APPDATA");
400 std::string path(wxGetHomeDir().ToStdString());
401 path += "\\AppData\\Roaming";
402 if (ocpn::exists(path)) {
403 winPluginDir = wxString(path.c_str());
404 ok = true;
405 wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
406 }
407 }
408 if (!ok) {
409 // {Documents and Settings}\.. on W7, else \ProgramData
410 winPluginDir = GetHomeDir();
411 }
412 wxFileName path(winPluginDir);
413 path.Normalize();
414 winPluginDir = path.GetFullPath() + "\\opencpn\\plugins";
415 wxLogMessage("Using private plugin dir: %s", winPluginDir);
416 return winPluginDir;
417}
418
420 if (m_PluginsDir.IsEmpty()) {
421 wxStandardPaths& std_path = GetStdPaths();
422
423 // Get the PlugIns directory location
424 m_PluginsDir = std_path.GetPluginsDir(); // linux: {prefix}/lib/opencpn
425 // Mac: appname.app/Contents/PlugIns
426#ifdef __WXMSW__
427 m_PluginsDir += _T("\\plugins"); // Windows: {exe dir}/plugins
428#endif
429 if (g_bportable) {
430 m_PluginsDir = GetHomeDir();
431 m_PluginsDir += _T("plugins");
432 }
433
434#ifdef __ANDROID__
435 // something like: data/data/org.opencpn.opencpn
436 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
437 fdir.RemoveLastDir();
438 m_PluginsDir = fdir.GetPath();
439#endif
440 }
441 return m_PluginsDir;
442}
443
444wxString* AbstractPlatform::GetPluginDirPtr() {
445 if (m_PluginsDir.IsEmpty()) GetPluginDir();
446 return &m_PluginsDir;
447}
448
449bool AbstractPlatform::isPlatformCapable(int flag) {
450#ifndef __ANDROID__
451 return true;
452#else
453 if (flag == PLATFORM_CAP_PLUGINS) {
454 long platver;
455 wxString tsdk(android_plat_spc.msdk);
456 if (tsdk.ToLong(&platver)) {
457 if (platver >= 11) return true;
458 }
459 } else if (flag == PLATFORM_CAP_FASTPAN) {
460 long platver;
461 wxString tsdk(android_plat_spc.msdk);
462 if (tsdk.ToLong(&platver)) {
463 if (platver >= 14) return true;
464 }
465 }
466
467 return false;
468#endif
469}
470
471void appendOSDirSlash(wxString* pString) {
472 wxChar sep = wxFileName::GetPathSeparator();
473 if (pString->Last() != sep) pString->Append(sep);
474}
475
476wxString AbstractPlatform::GetWritableDocumentsDir() {
477 wxString dir;
478
479#ifdef __ANDROID__
480 dir = androidGetExtStorageDir(); // Used for Chart storage, typically
481#else
482 wxStandardPaths& std_path = GetStdPaths();
483 dir = std_path.GetDocumentsDir();
484#endif
485 return dir;
486}
487
488bool AbstractPlatform::DetectOSDetail(OCPN_OSDetail* detail) {
489 if (!detail) return false;
490
491 // We take some defaults from build-time definitions
492 detail->osd_name = std::string(PKG_TARGET);
493 detail->osd_version = std::string(PKG_TARGET_VERSION);
494
495 // Now parse by basic platform
496#ifdef __linux__
497 if (wxFileExists(_T("/etc/os-release"))) {
498 wxTextFile release_file(_T("/etc/os-release"));
499 if (release_file.Open()) {
500 wxString val;
501 for (wxString str = release_file.GetFirstLine(); !release_file.Eof();
502 str = release_file.GetNextLine()) {
503 if (str.StartsWith(_T("NAME"))) {
504 val = str.AfterFirst('=').Mid(1);
505 val = val.Mid(0, val.Length() - 1);
506 if (val.Length()) detail->osd_name = std::string(val.mb_str());
507 } else if (str.StartsWith(_T("VERSION_ID"))) {
508 val = str.AfterFirst('=').Mid(1);
509 val = val.Mid(0, val.Length() - 1);
510 if (val.Length()) detail->osd_version = std::string(val.mb_str());
511 } else if (str.StartsWith(_T("ID="))) {
512 val = str.AfterFirst('=');
513 if (val.Length()) detail->osd_ID = ocpn::split(val.mb_str(), " ")[0];
514 } else if (str.StartsWith(_T("ID_LIKE"))) {
515 if (val.StartsWith('"')) {
516 val = str.AfterFirst('=').Mid(1);
517 val = val.Mid(0, val.Length() - 1);
518 } else {
519 val = str.AfterFirst('=');
520 }
521
522 if (val.Length()) {
523 detail->osd_names_like = ocpn::split(val.mb_str(), " ");
524 }
525 }
526 }
527
528 release_file.Close();
529 }
530 if (detail->osd_name == _T("Linux Mint")) {
531 if (wxFileExists(_T("/etc/upstream-release/lsb-release"))) {
532 wxTextFile upstream_release_file(
533 _T("/etc/upstream-release/lsb-release"));
534 if (upstream_release_file.Open()) {
535 wxString val;
536 for (wxString str = upstream_release_file.GetFirstLine();
537 !upstream_release_file.Eof();
538 str = upstream_release_file.GetNextLine()) {
539 if (str.StartsWith(_T("DISTRIB_RELEASE"))) {
540 val = str.AfterFirst('=').Mid(0);
541 val = val.Mid(0, val.Length());
542 if (val.Length()) detail->osd_version = std::string(val.mb_str());
543 }
544 }
545 upstream_release_file.Close();
546 }
547 }
548 }
549 }
550#endif
551
552 // Set the default processor architecture
553 detail->osd_arch = std::string("x86_64");
554
555 // then see what is actually running.
556 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
557 wxArchitecture arch = platformInfo.GetArchitecture();
558 if (arch == wxARCH_32) detail->osd_arch = std::string("i386");
559
560#ifdef ocpnARM
561 // arm supports a multiarch runtime environment
562 // That is, the OS may be 64 bit, but OCPN may be built as a 32 bit binary
563 // So, we cannot trust the wxPlatformInfo architecture determination.
564 detail->osd_arch = std::string("arm64");
565#ifdef ocpnARMHF
566 detail->osd_arch = std::string("armhf");
567#endif
568#endif
569
570#ifdef __ANDROID__
571 detail->osd_arch = std::string("arm64");
572 if (arch == wxARCH_32) detail->osd_arch = std::string("armhf");
573#endif
574
575#ifdef __WXOSX__
576 if (IsAppleSilicon() == 1) {
577 if (ProcessIsTranslated() != 1) {
578 detail->osd_arch = std::string("arm64");
579 } else {
580 detail->osd_arch = std::string("x86_64");
581 }
582 } else {
583 detail->osd_arch = std::string("x86_64");
584 }
585#endif
586
587 return true;
588}
589
590wxString& AbstractPlatform::GetConfigFileName() {
591 if (m_config_file_name.IsEmpty()) {
592 // Establish the location of the config file
593 wxStandardPaths& std_path = GetStdPaths();
594
595#ifdef __WXMSW__
596 m_config_file_name = "opencpn.ini";
597 m_config_file_name.Prepend(GetHomeDir());
598
599#elif defined __WXOSX__
600 m_config_file_name =
601 std_path.GetUserConfigDir(); // should be ~/Library/Preferences
602 appendOSDirSlash(&m_config_file_name);
603 m_config_file_name.Append("opencpn");
604 appendOSDirSlash(&m_config_file_name);
605 m_config_file_name.Append("opencpn.ini");
606#elif defined FLATPAK
607 m_config_file_name = GetPrivateDataDir();
608 m_config_file_name.Append("/opencpn.conf");
609 // Usually ~/.var/app/org.opencpn.OpenCPN/config/opencpn.conf
610#else
611 m_config_file_name = std_path.GetUserDataDir(); // should be ~/.opencpn
612 appendOSDirSlash(&m_config_file_name);
613 m_config_file_name.Append("opencpn.conf");
614#endif
615
616 if (g_bportable) {
617 m_config_file_name = GetHomeDir();
618#ifdef __WXMSW__
619 m_config_file_name += "opencpn.ini";
620#elif defined __WXOSX__
621 m_config_file_name += "opencpn.ini";
622#else
623 m_config_file_name += "opencpn.conf";
624#endif
625 }
626
627#ifdef __ANDROID__
628 m_config_file_name = androidGetPrivateDir();
629 appendOSDirSlash(&m_config_file_name);
630 m_config_file_name += "opencpn.conf";
631#endif
632 if (!g_configdir.empty()) {
633 m_config_file_name = g_configdir;
634 m_config_file_name.Append("/opencpn.conf");
635 }
636 }
637 return m_config_file_name;
638}
639
640bool BasePlatform::InitializeLogFile(void) {
641 // Establish Log File location
642 mlog_file = GetPrivateDataDir();
643 appendOSDirSlash(&mlog_file);
644
645#ifdef __WXOSX__
646
647 wxFileName LibPref(mlog_file); // starts like "~/Library/Preferences/opencpn"
648 LibPref.RemoveLastDir(); // takes off "opencpn"
649 LibPref.RemoveLastDir(); // takes off "Preferences"
650
651 mlog_file = LibPref.GetFullPath();
652 appendOSDirSlash(&mlog_file);
653
654 mlog_file.Append(_T("Logs/")); // so, on OS X, opencpn.log ends up in
655 // ~/Library/Logs which makes it accessible to
656 // Applications/Utilities/Console....
657#endif
658
659 // create the opencpn "home" directory if we need to
660 wxFileName wxHomeFiledir(GetHomeDir());
661 if (true != wxHomeFiledir.DirExists(wxHomeFiledir.GetPath()))
662 if (!wxHomeFiledir.Mkdir(wxHomeFiledir.GetPath())) {
663 wxASSERT_MSG(false, _T("Cannot create opencpn home directory"));
664 return false;
665 }
666
667 // create the opencpn "log" directory if we need to
668 wxFileName wxLogFiledir(mlog_file);
669 if (true != wxLogFiledir.DirExists(wxLogFiledir.GetPath())) {
670 if (!wxLogFiledir.Mkdir(wxLogFiledir.GetPath())) {
671 wxASSERT_MSG(false, _T("Cannot create opencpn log directory"));
672 return false;
673 }
674 }
675
676 mlog_file.Append(_T("opencpn.log"));
677 wxString logit = mlog_file;
678
679#ifdef __ANDROID__
680 wxCharBuffer abuf = mlog_file.ToUTF8();
681 qDebug() << "logfile " << abuf.data();
682#endif
683
684 // Constrain the size of the log file
685 if (::wxFileExists(mlog_file)) {
686 if (wxFileName::GetSize(mlog_file) > 1000000) {
687 wxString oldlog = mlog_file;
688 oldlog.Append(_T(".log"));
689 // Defer the showing of this messagebox until the system locale is
690 // established.
691 large_log_message = (_T("Old log will be moved to opencpn.log.log"));
692 ::wxRenameFile(mlog_file, oldlog);
693 }
694 }
695#ifdef __ANDROID__
696 if (::wxFileExists(mlog_file)) {
697 // Force new logfile for each instance
698 // TODO Remove this behaviour on Release
699 ::wxRemoveFile(mlog_file);
700 }
701#endif
702
703 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
704
705 auto logger = new OcpnLog(mlog_file.mb_str());
706 if (m_old_logger) {
707 delete m_old_logger;
708 }
709 m_old_logger = wxLog::SetActiveTarget(logger);
710
711 return true;
712}
713
714void AbstractPlatform::CloseLogFile(void) {
715 if (m_old_logger) {
716 delete wxLog::SetActiveTarget(m_old_logger);
717 m_old_logger = nullptr;
718 }
719}
720
722 if (g_bportable) {
723 wxString sep = wxFileName::GetPathSeparator();
724 wxString ret = GetPrivateDataDir() + sep + _T("plugins");
725 return ret;
726 }
727
728 if (m_pluginDataPath != "") {
729 return m_pluginDataPath;
730 }
731 wxString dirs("");
732#ifdef __ANDROID__
733 wxString pluginDir = GetPrivateDataDir() + "/plugins";
734 dirs += pluginDir;
735#else
736 auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
737 if (isFlatpacked()) {
738 dirs = "~/.var/app/org.opencpn.OpenCPN/data/opencpn/plugins";
739 } else if (osSystemId & wxOS_UNIX_LINUX) {
740 dirs = GetLinuxDataPath();
741 } else if (osSystemId & wxOS_WINDOWS) {
742 dirs = GetWinPluginBaseDir();
743 } else if (osSystemId & wxOS_MAC) {
744 dirs = "/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
745 dirs +=
746 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
747 }
748#endif
749
750 m_pluginDataPath = ExpandPaths(dirs, this);
751 if (m_pluginDataPath != "") {
752 m_pluginDataPath += ";";
753 }
754 m_pluginDataPath += GetPluginDir();
755 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
756 m_pluginDataPath.RemoveLast();
757 }
758 wxLogMessage("Using plugin data path: %s", m_pluginDataPath.mb_str().data());
759 return m_pluginDataPath;
760}
761
762#ifdef __ANDROID__
763void AbstractPlatform::ShowBusySpinner() {
764 if (!m_isBusy) {
765 androidShowBusyIcon();
766 m_isBusy = true;
767 }
768}
769#else
770void AbstractPlatform::ShowBusySpinner() {
771 if (!m_isBusy) {
772 ::wxBeginBusyCursor();
773 m_isBusy = true;
774 }
775}
776#endif
777
778#ifdef __ANDROID__
779void AbstractPlatform::HideBusySpinner() {
780 if (m_isBusy) {
781 androidHideBusyIcon();
782 m_isBusy = false;
783 }
784}
785#else
786void AbstractPlatform::HideBusySpinner() {
787 if (m_isBusy) {
788 ::wxEndBusyCursor();
789 m_isBusy = false;
790 }
791}
792#endif
793
794// getDisplaySize
795
796#if defined(__ANDROID__)
797wxSize BasePlatform::getDisplaySize() { return getAndroidDisplayDimensions(); }
798
799#else
800wxSize BasePlatform::getDisplaySize() { return wxSize(0, 0); }
801#endif
802
803// GetDisplaySizeMM
804double BasePlatform::GetDisplaySizeMM() {
805 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
806 return m_displaySizeMMOverride[0];
807 }
808 double ret = 0;
809
810#ifdef __ANDROID__
811 ret = GetAndroidDisplaySize();
812#endif
813
814 wxLogDebug("Detected display size (horizontal): %d mm", (int)ret);
815 return ret;
816}
817
818#if defined(__ANDROID__)
819double BasePlatform::GetDisplayDPmm() { return getAndroidDPmm(); }
820
821#else
822double BasePlatform::GetDisplayDPmm() {
823 if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
824 double r = getDisplaySize().x; // dots
825 return r / GetDisplaySizeMM();
826 } else {
827 // This is a console app... assuming 300 DPI ~ 12 DPmm
828 return 12.0;
829 }
830}
831#endif
832
834 double rv = 1.0;
835#ifdef __WXMSW__
836 if (win) rv = (double)(win->ToDIP(100)) / 100.;
837#endif
838 return rv;
839}
840
841unsigned int AbstractPlatform::GetSelectRadiusPix() {
842 return GetDisplayDPmm() *
843 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
844}
845
846#ifdef __WXMSW__
847
848#define NAME_SIZE 128
849
850const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
851 0x00, 0x2b, 0xe1, 0x03, 0x18};
852
853// Assumes hDevRegKey is valid
854bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, int* WidthMm,
855 int* HeightMm) {
856 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
857 TCHAR valueName[NAME_SIZE];
858
859 BYTE EDIDdata[1024];
860 DWORD edidsize = sizeof(EDIDdata);
861
862 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
863 ++i) {
864 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
865 &AcutalValueNameLength, NULL, &dwType,
866 EDIDdata, // buffer
867 &edidsize); // buffer size
868
869 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T("EDID")))
870 continue;
871
872 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
873 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
874
875 return true; // valid EDID found
876 }
877
878 return false; // EDID not found
879}
880
881bool GetSizeForDevID(wxString& TargetDevID, int* WidthMm, int* HeightMm) {
882 HDEVINFO devInfo =
883 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR, // class GUID
884 NULL, // enumerator
885 NULL, // HWND
886 DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
887 NULL, // device info, create a new one.
888 NULL, // machine name, local machine
889 NULL); // reserved
890
891 if (NULL == devInfo) return false;
892
893 bool bRes = false;
894
895 for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
896 SP_DEVINFO_DATA devInfoData;
897 memset(&devInfoData, 0, sizeof(devInfoData));
898 devInfoData.cbSize = sizeof(devInfoData);
899
900 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
901 wchar_t Instance[80];
902 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
903 NULL);
904 wxString instance(Instance);
905 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND) continue;
906
907 HKEY hDevRegKey = SetupDiOpenDevRegKey(
908 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
909
910 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) continue;
911
912 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
913
914 RegCloseKey(hDevRegKey);
915 }
916 }
917 SetupDiDestroyDeviceInfoList(devInfo);
918 return bRes;
919}
920
921bool AbstractPlatform::GetWindowsMonitorSize(int* width, int* height) {
922 bool bFoundDevice = true;
923
924 if (m_monitorWidth < 10) {
925 int WidthMm = 0;
926 int HeightMm = 0;
927
928 DISPLAY_DEVICE dd;
929 dd.cb = sizeof(dd);
930 DWORD dev = 0; // device index
931 int id = 1; // monitor number, as used by Display Properties > Settings
932
933 wxString DeviceID;
934 bFoundDevice = false;
935 while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice) {
936 DISPLAY_DEVICE ddMon;
937 ZeroMemory(&ddMon, sizeof(ddMon));
938 ddMon.cb = sizeof(ddMon);
939 DWORD devMon = 0;
940
941 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
942 !bFoundDevice) {
943 if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
944 !(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
945 DeviceID = wxString(ddMon.DeviceID, wxConvUTF8);
946 DeviceID = DeviceID.Mid(8);
947 DeviceID = DeviceID.Mid(0, DeviceID.Find('\\'));
948
949 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
950 }
951 devMon++;
952
953 ZeroMemory(&ddMon, sizeof(ddMon));
954 ddMon.cb = sizeof(ddMon);
955 }
956
957 ZeroMemory(&dd, sizeof(dd));
958 dd.cb = sizeof(dd);
959 dev++;
960 }
961 m_monitorWidth = WidthMm;
962 m_monitorHeight = HeightMm;
963 }
964
965 if (width) *width = m_monitorWidth;
966 if (height) *height = m_monitorHeight;
967
968 return bFoundDevice;
969}
970
971#endif // __WXMSW__
972
973int BasePlatform::GetSvgStdIconSize(const wxWindow* w, bool touch) {
974 double size = w->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
975#if wxCHECK_VERSION(3, 1, 2)
976 // Apply scale factor, mostly for Windows. Other platforms
977 // does this in the toolkits, ToDIP() is aware of this.
978 size *= static_cast<double>(w->ToDIP(100)) / 100.;
979#endif
980 // Force minimum physical size for touch screens
981 if (touch) {
982 double pixel_per_mm = wxGetDisplaySize().x / GetDisplaySizeMM();
983 size = std::max(size, 7.0 * pixel_per_mm);
984 }
985 return std::round(size);
986}
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:88
Global variables reflecting command line options and arguments.
wxString g_winPluginDir
Base plugin directory on Windows.
Global variables stored in configuration file.
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.