49#include <wx/apptrait.h>
51#include <wx/filename.h>
52#include <wx/stdpaths.h>
53#include <wx/textfile.h>
54#include <wx/tokenzr.h>
58#include "model/base_platform.h"
60#include "model/config_vars.h"
62#include "model/ocpn_utils.h"
67#include "androidUTIL.h"
71#include "model/macutils.h"
75static const char PATH_SEP =
';';
77static const char PATH_SEP =
':';
80bool AbstractPlatform::m_isBusy =
false;
82static const char*
const DEFAULT_XDG_DATA_DIRS =
83 "~/.local/share:/usr/local/share:/usr/share";
85void appendOSDirSlash(wxString* pString);
93static bool checkIfFlatpacked() {
95 if (!wxGetEnv(
"FLATPAK_ID", &
id)) {
98 return id ==
"org.opencpn.OpenCPN";
103static wxString GetLinuxDataPath() {
105 if (wxGetEnv(
"XDG_DATA_DIRS", &dirs)) {
106 dirs = wxString(
"~/.local/share:") + dirs;
108 dirs = DEFAULT_XDG_DATA_DIRS;
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);
117 if (!dir.EndsWith(
"/opencpn/plugins")) {
118 dir +=
"/opencpn/plugins";
120 s += dir + (tokens.HasMoreTokens() ?
";" :
"");
126 wxStringTokenizer tokens(paths,
';');
128 while (tokens.HasMoreTokens()) {
129 wxFileName filename(tokens.GetNextToken());
130 filename.Normalize();
131 s += platform->NormalizePath(filename.GetFullPath());
132 if (tokens.HasMoreTokens()) {
140BasePlatform::BasePlatform() {
142 m_isFlatpacked = checkIfFlatpacked();
144 DetectOSDetail(m_osDetail);
153BasePlatform::~BasePlatform() {
155 delete wxLog::SetActiveTarget(
new wxLogStderr());
162wxStandardPaths& AbstractPlatform::GetStdPaths() {
164 return wxStandardPaths::Get();
166 return *
dynamic_cast<wxStandardPaths*
>(
167 &(wxTheApp->GetTraits())->GetStandardPaths());
171wxString AbstractPlatform::NormalizePath(
const wxString& full_path) {
175 wxString path(full_path);
179 path = f.GetFullPath();
185wxString& AbstractPlatform::GetHomeDir() {
186 if (m_homeDir.IsEmpty()) {
188 wxStandardPaths& std_path = GetStdPaths();
193 std_path.SetInstallPrefix(wxString(PREFIX, wxConvUTF8));
201 m_homeDir = std_path.GetUserConfigDir();
205 m_homeDir = androidGetHomeDir();
209 wxFileName path(GetExePath());
210 m_homeDir = path.GetPath();
214 appendOSDirSlash(&m_homeDir);
215 m_homeDir.Append(_T(
"opencpn"));
218 appendOSDirSlash(&m_homeDir);
224wxString& AbstractPlatform::GetExePath() {
225 if (m_exePath.IsEmpty()) {
226 wxStandardPaths& std_path = GetStdPaths();
227 m_exePath = std_path.GetExecutablePath();
233wxString* AbstractPlatform::GetSharedDataDirPtr() {
234 if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
240 return &m_PrivateDataDir;
243wxString& AbstractPlatform::GetSharedDataDir() {
244 if (m_SData_Dir.IsEmpty()) {
255 wxStandardPaths& std_path = GetStdPaths();
256 m_SData_Dir = std_path.GetDataDir();
257 appendOSDirSlash(&m_SData_Dir);
260 m_SData_Dir = androidGetSharedDir();
263 if (g_bportable) m_SData_Dir = GetHomeDir();
269wxString GetPluginDataDir(
const char* plugin_name) {
270 static const wxString sep = wxFileName::GetPathSeparator();
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);
279 if (!tryDir.Open(tryDirName.GetFullPath()))
continue;
281 bool more = tryDir.GetFirst(&next);
283 if (next == plugin_name) {
284 next = next.Prepend(tryDirName.GetFullPath() + sep);
285 wxLogMessage(_T(
"PlugInManager: using data dir: %s"), next);
288 more = tryDir.GetNext(&next);
292 wxLogMessage(_T(
"Warning: no data directory found, using \"\""));
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;
310 if (m_PrivateDataDir.IsEmpty()) {
312 wxStandardPaths& std_path = GetStdPaths();
318 std::string config_home;
319 if (getenv(
"XDG_CONFIG_HOME")) {
320 config_home = getenv(
"XDG_CONFIG_HOME");
322 config_home = getenv(
"HOME");
323 config_home +=
"/.var/app/org.opencpn.OpenCPN/config";
325 m_PrivateDataDir = config_home +
"/opencpn";
327#elif defined __WXOSX__
329 std_path.GetUserConfigDir();
330 appendOSDirSlash(&m_PrivateDataDir);
331 m_PrivateDataDir.Append(_T(
"opencpn"));
333 m_PrivateDataDir = std_path.GetUserDataDir();
336 if (g_bportable) m_PrivateDataDir = GetHomeDir();
337 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
338 m_PrivateDataDir.RemoveLast();
341 m_PrivateDataDir = androidGetPrivateDir();
344 return m_PrivateDataDir;
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());
356 return fn.GetFullPath();
358 wxString winPluginDir;
361 winPluginDir = (GetHomeDir() + _T(
"plugins"));
362 if (ocpn::exists(winPluginDir.ToStdString())) {
363 wxLogMessage(
"Using portable plugin dir: %s", winPluginDir);
368 bool ok = wxGetEnv(_T(
"LOCALAPPDATA"), &winPluginDir);
370 wxLogMessage(
"winPluginDir: Cannot lookup LOCALAPPDATA");
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());
382 ok = wxGetEnv(_T(
"APPDATA"), &winPluginDir);
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());
392 wxLogMessage(
"winPluginDir: using %s", winPluginDir.mb_str().data());
397 winPluginDir = GetHomeDir();
399 wxFileName path(winPluginDir);
401 winPluginDir = path.GetFullPath() +
"\\opencpn\\plugins";
402 wxLogMessage(
"Using private plugin dir: %s", winPluginDir);
407 if (m_PluginsDir.IsEmpty()) {
408 wxStandardPaths& std_path = GetStdPaths();
411 m_PluginsDir = std_path.GetPluginsDir();
414 m_PluginsDir += _T(
"\\plugins");
417 m_PluginsDir = GetHomeDir();
418 m_PluginsDir += _T(
"plugins");
423 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
424 fdir.RemoveLastDir();
425 m_PluginsDir = fdir.GetPath();
431wxString* AbstractPlatform::GetPluginDirPtr() {
433 return &m_PluginsDir;
436bool AbstractPlatform::isPlatformCapable(
int flag) {
440 if (flag == PLATFORM_CAP_PLUGINS) {
442 wxString tsdk(android_plat_spc.msdk);
443 if (tsdk.ToLong(&platver)) {
444 if (platver >= 11)
return true;
446 }
else if (flag == PLATFORM_CAP_FASTPAN) {
448 wxString tsdk(android_plat_spc.msdk);
449 if (tsdk.ToLong(&platver)) {
450 if (platver >= 14)
return true;
458void appendOSDirSlash(wxString* pString) {
459 wxChar sep = wxFileName::GetPathSeparator();
460 if (pString->Last() != sep) pString->Append(sep);
463wxString AbstractPlatform::GetWritableDocumentsDir() {
467 dir = androidGetExtStorageDir();
469 wxStandardPaths& std_path = GetStdPaths();
470 dir = std_path.GetDocumentsDir();
475bool AbstractPlatform::DetectOSDetail(
OCPN_OSDetail* detail) {
476 if (!detail)
return false;
479 detail->osd_name = std::string(PKG_TARGET);
480 detail->osd_version = std::string(PKG_TARGET_VERSION);
484 if (wxFileExists(_T(
"/etc/os-release"))) {
485 wxTextFile release_file(_T(
"/etc/os-release"));
486 if (release_file.Open()) {
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);
506 val = str.AfterFirst(
'=');
510 detail->osd_names_like = ocpn::split(val.mb_str(),
" ");
515 release_file.Close();
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()) {
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());
532 upstream_release_file.Close();
540 detail->osd_arch = std::string(
"x86_64");
543 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
544 wxArchitecture arch = platformInfo.GetArchitecture();
545 if (arch == wxARCH_32) detail->osd_arch = std::string(
"i386");
551 detail->osd_arch = std::string(
"arm64");
553 detail->osd_arch = std::string(
"armhf");
558 detail->osd_arch = std::string(
"arm64");
559 if (arch == wxARCH_32) detail->osd_arch = std::string(
"armhf");
563 if (IsAppleSilicon() == 1) {
564 if (ProcessIsTranslated() != 1) {
565 detail->osd_arch = std::string(
"arm64");
567 detail->osd_arch = std::string(
"x86_64");
570 detail->osd_arch = std::string(
"x86_64");
577wxString& AbstractPlatform::GetConfigFileName() {
578 if (m_config_file_name.IsEmpty()) {
580 wxStandardPaths& std_path = GetStdPaths();
583 m_config_file_name =
"opencpn.ini";
584 m_config_file_name.Prepend(GetHomeDir());
586#elif defined __WXOSX__
588 std_path.GetUserConfigDir();
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");
595 m_config_file_name.Append(
"/opencpn.conf");
598 m_config_file_name = std_path.GetUserDataDir();
599 appendOSDirSlash(&m_config_file_name);
600 m_config_file_name.Append(
"opencpn.conf");
604 m_config_file_name = GetHomeDir();
606 m_config_file_name +=
"opencpn.ini";
607#elif defined __WXOSX__
608 m_config_file_name +=
"opencpn.ini";
610 m_config_file_name +=
"opencpn.conf";
615 m_config_file_name = androidGetPrivateDir();
616 appendOSDirSlash(&m_config_file_name);
617 m_config_file_name +=
"opencpn.conf";
619 if (!g_configdir.empty()) {
620 m_config_file_name = g_configdir;
621 m_config_file_name.Append(
"/opencpn.conf");
624 return m_config_file_name;
627bool BasePlatform::InitializeLogFile(
void) {
630 appendOSDirSlash(&mlog_file);
634 wxFileName LibPref(mlog_file);
635 LibPref.RemoveLastDir();
636 LibPref.RemoveLastDir();
638 mlog_file = LibPref.GetFullPath();
639 appendOSDirSlash(&mlog_file);
641 mlog_file.Append(_T(
"Logs/"));
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"));
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"));
663 mlog_file.Append(_T(
"opencpn.log"));
664 wxString logit = mlog_file;
667 wxCharBuffer abuf = mlog_file.ToUTF8();
668 qDebug() <<
"logfile " << abuf.data();
672 if (::wxFileExists(mlog_file)) {
673 if (wxFileName::GetSize(mlog_file) > 1000000) {
674 wxString oldlog = mlog_file;
675 oldlog.Append(_T(
".log"));
678 large_log_message = (_T(
"Old log will be moved to opencpn.log.log"));
679 ::wxRenameFile(mlog_file, oldlog);
683 if (::wxFileExists(mlog_file)) {
686 ::wxRemoveFile(mlog_file);
690 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
692 auto logger =
new OcpnLog(mlog_file.mb_str());
696 m_old_logger = wxLog::SetActiveTarget(logger);
701void AbstractPlatform::CloseLogFile(
void) {
703 delete wxLog::SetActiveTarget(m_old_logger);
704 m_old_logger =
nullptr;
710 wxString sep = wxFileName::GetPathSeparator();
715 if (m_pluginDataPath !=
"") {
716 return m_pluginDataPath;
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) {
730 }
else if (osSystemId & wxOS_MAC) {
731 dirs =
"/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
733 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
737 m_pluginDataPath = ExpandPaths(dirs,
this);
738 if (m_pluginDataPath !=
"") {
739 m_pluginDataPath +=
";";
742 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
743 m_pluginDataPath.RemoveLast();
745 wxLogMessage(
"Using plugin data path: %s", m_pluginDataPath.mb_str().data());
746 return m_pluginDataPath;
750void AbstractPlatform::ShowBusySpinner() {
752 androidShowBusyIcon();
757void AbstractPlatform::ShowBusySpinner() {
759 ::wxBeginBusyCursor();
766void AbstractPlatform::HideBusySpinner() {
768 androidHideBusyIcon();
773void AbstractPlatform::HideBusySpinner() {
783#if defined(__ANDROID__)
784wxSize BasePlatform::getDisplaySize() {
return getAndroidDisplayDimensions(); }
787wxSize BasePlatform::getDisplaySize() {
return wxSize(0, 0); }
791double BasePlatform::GetDisplaySizeMM() {
792 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
793 return m_displaySizeMMOverride[0];
798 ret = GetAndroidDisplaySize();
801 wxLogDebug(
"Detected display size (horizontal): %d mm", (
int)ret);
805#if defined(__ANDROID__)
806double BasePlatform::GetDisplayDPmm() {
return getAndroidDPmm(); }
809double BasePlatform::GetDisplayDPmm() {
810 if (
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
811 double r = getDisplaySize().x;
812 return r / GetDisplaySizeMM();
823 if (win) rv = (double)(win->ToDIP(100)) / 100.;
828unsigned int AbstractPlatform::GetSelectRadiusPix() {
829 return GetDisplayDPmm() *
830 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
837const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
838 0x00, 0x2b, 0xe1, 0x03, 0x18};
841bool GetMonitorSizeFromEDID(
const HKEY hDevRegKey,
int* WidthMm,
843 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
844 TCHAR valueName[NAME_SIZE];
847 DWORD edidsize =
sizeof(EDIDdata);
849 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
851 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
852 &AcutalValueNameLength, NULL, &dwType,
856 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T(
"EDID")))
859 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
860 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
868bool GetSizeForDevID(wxString& TargetDevID,
int* WidthMm,
int* HeightMm) {
870 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR,
878 if (NULL == devInfo)
return false;
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);
887 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
888 wchar_t Instance[80];
889 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
891 wxString instance(Instance);
892 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND)
continue;
894 HKEY hDevRegKey = SetupDiOpenDevRegKey(
895 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
897 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
continue;
899 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
901 RegCloseKey(hDevRegKey);
904 SetupDiDestroyDeviceInfoList(devInfo);
908bool AbstractPlatform::GetWindowsMonitorSize(
int* width,
int* height) {
909 bool bFoundDevice =
true;
911 if (m_monitorWidth < 10) {
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);
928 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
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(
'\\'));
936 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
940 ZeroMemory(&ddMon,
sizeof(ddMon));
941 ddMon.cb =
sizeof(ddMon);
944 ZeroMemory(&dd,
sizeof(dd));
948 m_monitorWidth = WidthMm;
949 m_monitorHeight = HeightMm;
952 if (width) *width = m_monitorWidth;
953 if (height) *height = m_monitorHeight;
Customized logger class appending to a file providing:
Global variables reflecting command line options and arguments.
Enhanced logging interface on top of wx/log.h.
PlugIn Object Definition/API.