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