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