49#include <wx/apptrait.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>
59#include "model/base_platform.h"
61#include "model/config_vars.h"
68#include "androidUTIL.h"
72#include "model/macutils.h"
76static const char PATH_SEP =
';';
78static const char PATH_SEP =
':';
81bool AbstractPlatform::m_isBusy =
false;
83static const char*
const DEFAULT_XDG_DATA_DIRS =
84 "~/.local/share:/usr/local/share:/usr/share";
86void appendOSDirSlash(wxString* pString);
94static inline bool IsWindows() {
95 return wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS;
98static bool checkIfFlatpacked() {
100 if (!wxGetEnv(
"FLATPAK_ID", &
id)) {
103 return id ==
"org.opencpn.OpenCPN";
108static wxString GetLinuxDataPath() {
110 if (wxGetEnv(
"XDG_DATA_DIRS", &dirs)) {
111 dirs = wxString(
"~/.local/share:") + dirs;
113 dirs = DEFAULT_XDG_DATA_DIRS;
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);
122 if (!dir.EndsWith(
"/opencpn/plugins")) {
123 dir +=
"/opencpn/plugins";
125 s += dir + (tokens.HasMoreTokens() ?
";" :
"");
131 wxStringTokenizer tokens(paths,
';');
133 while (tokens.HasMoreTokens()) {
134 wxFileName filename(tokens.GetNextToken());
135 filename.Normalize();
136 s += platform->NormalizePath(filename.GetFullPath());
137 if (tokens.HasMoreTokens()) {
145BasePlatform::BasePlatform() {
147 m_isFlatpacked = checkIfFlatpacked();
149 DetectOSDetail(m_osDetail);
158BasePlatform::~BasePlatform() {
160 delete wxLog::SetActiveTarget(
new wxLogStderr());
167wxStandardPaths& AbstractPlatform::GetStdPaths() {
169 return wxStandardPaths::Get();
171 return *
dynamic_cast<wxStandardPaths*
>(
172 &(wxTheApp->GetTraits())->GetStandardPaths());
176wxString AbstractPlatform::NormalizePath(
const wxString& full_path) {
180 wxString path(full_path);
184 path = f.GetFullPath();
190wxString& AbstractPlatform::GetHomeDir() {
191 if (m_homeDir.IsEmpty()) {
193 wxStandardPaths& std_path = GetStdPaths();
198 std_path.SetInstallPrefix(wxString(PREFIX, wxConvUTF8));
206 m_homeDir = std_path.GetUserConfigDir();
210 m_homeDir = androidGetHomeDir();
214 wxFileName path(GetExePath());
215 m_homeDir = path.GetPath();
219 appendOSDirSlash(&m_homeDir);
220 m_homeDir.Append(_T(
"opencpn"));
223 appendOSDirSlash(&m_homeDir);
229wxString& AbstractPlatform::GetExePath() {
230 if (m_exePath.IsEmpty()) {
231 wxStandardPaths& std_path = GetStdPaths();
232 m_exePath = std_path.GetExecutablePath();
238wxString* AbstractPlatform::GetSharedDataDirPtr() {
239 if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
245 return &m_PrivateDataDir;
248wxString& AbstractPlatform::GetSharedDataDir() {
249 if (m_SData_Dir.IsEmpty()) {
260 wxStandardPaths& std_path = GetStdPaths();
261 m_SData_Dir = std_path.GetDataDir();
262 appendOSDirSlash(&m_SData_Dir);
265 m_SData_Dir = androidGetSharedDir();
268 if (g_bportable) m_SData_Dir = GetHomeDir();
274wxString GetPluginDataDir(
const char* plugin_name) {
275 static const wxString sep = wxFileName::GetPathSeparator();
278 wxLogMessage(_T(
"PlugInManager: Using data dirs from: ") + datadirs);
279 wxStringTokenizer dirs(datadirs,
";");
280 while (dirs.HasMoreTokens()) {
281 wxString dir = dirs.GetNextToken();
282 wxFileName tryDirName(dir);
284 if (!tryDir.Open(tryDirName.GetFullPath()))
continue;
286 bool more = tryDir.GetFirst(&next);
288 if (next == plugin_name) {
289 next = next.Prepend(tryDirName.GetFullPath() + sep);
290 wxLogMessage(_T(
"PlugInManager: using data dir: %s"), next);
293 more = tryDir.GetNext(&next);
297 wxLogMessage(_T(
"Warning: no data directory found, using \"\""));
302 if (!m_PrivateDataDir.IsEmpty() && g_configdir.empty())
303 return m_PrivateDataDir;
304 if (!g_configdir.empty()) {
305 wxString path = g_configdir;
306 if (path.Last() == wxFileName::GetPathSeparator()) path.RemoveLast();
307 m_default_private_datadir = path;
308 return m_default_private_datadir;
315 if (m_PrivateDataDir.IsEmpty()) {
317 wxStandardPaths& std_path = GetStdPaths();
323 std::string config_home;
324 if (getenv(
"XDG_CONFIG_HOME")) {
325 config_home = getenv(
"XDG_CONFIG_HOME");
327 config_home = getenv(
"HOME");
328 config_home +=
"/.var/app/org.opencpn.OpenCPN/config";
330 m_PrivateDataDir = config_home +
"/opencpn";
332#elif defined __WXOSX__
334 std_path.GetUserConfigDir();
335 appendOSDirSlash(&m_PrivateDataDir);
336 m_PrivateDataDir.Append(_T(
"opencpn"));
338 m_PrivateDataDir = std_path.GetUserDataDir();
341 if (g_bportable) m_PrivateDataDir = GetHomeDir();
342 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
343 m_PrivateDataDir.RemoveLast();
346 m_PrivateDataDir = androidGetPrivateDir();
349 return m_PrivateDataDir;
353 if (g_winPluginDir !=
"") {
354 wxLogMessage(
"winPluginDir: Using value from ini file.");
355 wxFileName fn(g_winPluginDir);
356 if (!fn.DirExists()) {
357 wxLogWarning(
"Plugin dir %s does not exist",
358 fn.GetFullPath().mb_str().data());
361 return fn.GetFullPath();
363 wxString winPluginDir;
366 winPluginDir = (GetHomeDir() + _T(
"plugins"));
368 wxLogMessage(
"Using portable plugin dir: %s", winPluginDir);
373 bool ok = wxGetEnv(_T(
"LOCALAPPDATA"), &winPluginDir);
375 wxLogMessage(
"winPluginDir: Cannot lookup LOCALAPPDATA");
377 std::string path(wxGetHomeDir().ToStdString());
378 path +=
"\\AppData\\Local";
380 winPluginDir = wxString(path.c_str());
381 wxLogMessage(
"winPluginDir: using %s", winPluginDir.mb_str().data());
387 ok = wxGetEnv(_T(
"APPDATA"), &winPluginDir);
391 wxLogMessage(
"winPluginDir: Cannot lookup APPDATA");
392 std::string path(wxGetHomeDir().ToStdString());
393 path +=
"\\AppData\\Roaming";
395 winPluginDir = wxString(path.c_str());
397 wxLogMessage(
"winPluginDir: using %s", winPluginDir.mb_str().data());
402 winPluginDir = GetHomeDir();
404 wxFileName path(winPluginDir);
406 winPluginDir = path.GetFullPath() +
"\\opencpn\\plugins";
407 wxLogMessage(
"Using private plugin dir: %s", winPluginDir);
412 if (m_PluginsDir.IsEmpty()) {
413 wxStandardPaths& std_path = GetStdPaths();
416 m_PluginsDir = std_path.GetPluginsDir();
419 m_PluginsDir += _T(
"\\plugins");
422 m_PluginsDir = GetHomeDir();
423 m_PluginsDir += _T(
"plugins");
428 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
429 fdir.RemoveLastDir();
430 m_PluginsDir = fdir.GetPath();
436wxString* AbstractPlatform::GetPluginDirPtr() {
438 return &m_PluginsDir;
441bool AbstractPlatform::isPlatformCapable(
int flag) {
445 if (flag == PLATFORM_CAP_PLUGINS) {
447 wxString tsdk(android_plat_spc.msdk);
448 if (tsdk.ToLong(&platver)) {
449 if (platver >= 11)
return true;
451 }
else if (flag == PLATFORM_CAP_FASTPAN) {
453 wxString tsdk(android_plat_spc.msdk);
454 if (tsdk.ToLong(&platver)) {
455 if (platver >= 14)
return true;
463void appendOSDirSlash(wxString* pString) {
464 wxChar sep = wxFileName::GetPathSeparator();
465 if (pString->Last() != sep) pString->Append(sep);
468wxString AbstractPlatform::GetWritableDocumentsDir() {
472 dir = androidGetExtStorageDir();
474 wxStandardPaths& std_path = GetStdPaths();
475 dir = std_path.GetDocumentsDir();
480bool AbstractPlatform::DetectOSDetail(
OCPN_OSDetail* detail) {
481 if (!detail)
return false;
484 detail->osd_name = std::string(PKG_TARGET);
485 detail->osd_version = std::string(PKG_TARGET_VERSION);
489 if (wxFileExists(_T(
"/etc/os-release"))) {
490 wxTextFile release_file(_T(
"/etc/os-release"));
491 if (release_file.Open()) {
493 for (wxString str = release_file.GetFirstLine(); !release_file.Eof();
494 str = release_file.GetNextLine()) {
495 if (str.StartsWith(_T(
"NAME"))) {
496 val = str.AfterFirst(
'=').Mid(1);
497 val = val.Mid(0, val.Length() - 1);
498 if (val.Length()) detail->osd_name = std::string(val.mb_str());
499 }
else if (str.StartsWith(_T(
"VERSION_ID"))) {
500 val = str.AfterFirst(
'=').Mid(1);
501 val = val.Mid(0, val.Length() - 1);
502 if (val.Length()) detail->osd_version = std::string(val.mb_str());
503 }
else if (str.StartsWith(_T(
"ID="))) {
504 val = str.AfterFirst(
'=');
505 if (val.Length()) detail->osd_ID =
ocpn::split(val.mb_str(),
" ")[0];
506 }
else if (str.StartsWith(_T(
"ID_LIKE"))) {
507 if (val.StartsWith(
'"')) {
508 val = str.AfterFirst(
'=').Mid(1);
509 val = val.Mid(0, val.Length() - 1);
511 val = str.AfterFirst(
'=');
515 detail->osd_names_like =
ocpn::split(val.mb_str(),
" ");
520 release_file.Close();
522 if (detail->osd_name == _T(
"Linux Mint")) {
523 if (wxFileExists(_T(
"/etc/upstream-release/lsb-release"))) {
524 wxTextFile upstream_release_file(
525 _T(
"/etc/upstream-release/lsb-release"));
526 if (upstream_release_file.Open()) {
528 for (wxString str = upstream_release_file.GetFirstLine();
529 !upstream_release_file.Eof();
530 str = upstream_release_file.GetNextLine()) {
531 if (str.StartsWith(_T(
"DISTRIB_RELEASE"))) {
532 val = str.AfterFirst(
'=').Mid(0);
533 val = val.Mid(0, val.Length());
534 if (val.Length()) detail->osd_version = std::string(val.mb_str());
537 upstream_release_file.Close();
545 detail->osd_arch = std::string(
"x86_64");
548 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
549 wxArchitecture arch = platformInfo.GetArchitecture();
550 if (arch == wxARCH_32) detail->osd_arch = std::string(
"i386");
556 detail->osd_arch = std::string(
"arm64");
558 detail->osd_arch = std::string(
"armhf");
563 detail->osd_arch = std::string(
"arm64");
564 if (arch == wxARCH_32) detail->osd_arch = std::string(
"armhf");
568 if (IsAppleSilicon() == 1) {
569 if (ProcessIsTranslated() != 1) {
570 detail->osd_arch = std::string(
"arm64");
572 detail->osd_arch = std::string(
"x86_64");
575 detail->osd_arch = std::string(
"x86_64");
582wxString& AbstractPlatform::GetConfigFileName() {
583 if (m_config_file_name.IsEmpty()) {
585 wxStandardPaths& std_path = GetStdPaths();
588 m_config_file_name =
"opencpn.ini";
589 m_config_file_name.Prepend(GetHomeDir());
591#elif defined __WXOSX__
593 std_path.GetUserConfigDir();
594 appendOSDirSlash(&m_config_file_name);
595 m_config_file_name.Append(
"opencpn");
596 appendOSDirSlash(&m_config_file_name);
597 m_config_file_name.Append(
"opencpn.ini");
600 m_config_file_name.Append(
"/opencpn.conf");
603 m_config_file_name = std_path.GetUserDataDir();
604 appendOSDirSlash(&m_config_file_name);
605 m_config_file_name.Append(
"opencpn.conf");
609 m_config_file_name = GetHomeDir();
611 m_config_file_name +=
"opencpn.ini";
612#elif defined __WXOSX__
613 m_config_file_name +=
"opencpn.ini";
615 m_config_file_name +=
"opencpn.conf";
620 m_config_file_name = androidGetPrivateDir();
621 appendOSDirSlash(&m_config_file_name);
622 m_config_file_name +=
"opencpn.conf";
624 if (!g_configdir.empty()) {
625 m_config_file_name = g_configdir;
626 m_config_file_name.Append(
"/opencpn.conf");
629 return m_config_file_name;
632bool BasePlatform::InitializeLogFile(
void) {
635 appendOSDirSlash(&mlog_file);
639 wxFileName LibPref(mlog_file);
640 LibPref.RemoveLastDir();
641 LibPref.RemoveLastDir();
643 mlog_file = LibPref.GetFullPath();
644 appendOSDirSlash(&mlog_file);
646 mlog_file.Append(_T(
"Logs/"));
652 wxFileName wxHomeFiledir(GetHomeDir());
653 if (
true != wxHomeFiledir.DirExists(wxHomeFiledir.GetPath()))
654 if (!wxHomeFiledir.Mkdir(wxHomeFiledir.GetPath())) {
655 wxASSERT_MSG(
false, _T(
"Cannot create opencpn home directory"));
660 wxFileName wxLogFiledir(mlog_file);
661 if (
true != wxLogFiledir.DirExists(wxLogFiledir.GetPath())) {
662 if (!wxLogFiledir.Mkdir(wxLogFiledir.GetPath())) {
663 wxASSERT_MSG(
false, _T(
"Cannot create opencpn log directory"));
668 mlog_file.Append(_T(
"opencpn.log"));
669 wxString logit = mlog_file;
672 wxCharBuffer abuf = mlog_file.ToUTF8();
673 qDebug() <<
"logfile " << abuf.data();
677 if (::wxFileExists(mlog_file)) {
678 if (wxFileName::GetSize(mlog_file) > 1000000) {
679 wxString oldlog = mlog_file;
680 oldlog.Append(_T(
".log"));
683 large_log_message = (_T(
"Old log will be moved to opencpn.log.log"));
684 ::wxRenameFile(mlog_file, oldlog);
688 if (::wxFileExists(mlog_file)) {
691 ::wxRemoveFile(mlog_file);
695 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
697 auto logger =
new OcpnLog(mlog_file.mb_str());
701 m_old_logger = wxLog::SetActiveTarget(logger);
706void AbstractPlatform::CloseLogFile(
void) {
708 delete wxLog::SetActiveTarget(m_old_logger);
709 m_old_logger =
nullptr;
715 wxString sep = wxFileName::GetPathSeparator();
720 if (m_pluginDataPath !=
"") {
721 return m_pluginDataPath;
728 auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
729 if (isFlatpacked()) {
730 dirs =
"~/.var/app/org.opencpn.OpenCPN/data/opencpn/plugins";
731 }
else if (osSystemId & wxOS_UNIX_LINUX) {
732 dirs = GetLinuxDataPath();
733 }
else if (osSystemId & wxOS_WINDOWS) {
735 }
else if (osSystemId & wxOS_MAC) {
736 dirs =
"/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
738 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
742 m_pluginDataPath = ExpandPaths(dirs,
this);
743 if (m_pluginDataPath !=
"") {
744 m_pluginDataPath +=
";";
747 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
748 m_pluginDataPath.RemoveLast();
750 wxLogMessage(
"Using plugin data path: %s", m_pluginDataPath.mb_str().data());
751 return m_pluginDataPath;
755void AbstractPlatform::ShowBusySpinner() {
757 androidShowBusyIcon();
762void AbstractPlatform::ShowBusySpinner() {
764 ::wxBeginBusyCursor();
771void AbstractPlatform::HideBusySpinner() {
773 androidHideBusyIcon();
778void AbstractPlatform::HideBusySpinner() {
788#if defined(__ANDROID__)
789wxSize BasePlatform::getDisplaySize() {
return getAndroidDisplayDimensions(); }
792wxSize BasePlatform::getDisplaySize() {
return wxSize(0, 0); }
796double BasePlatform::GetDisplaySizeMM() {
797 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
798 return m_displaySizeMMOverride[0];
803 ret = GetAndroidDisplaySize();
806 wxLogDebug(
"Detected display size (horizontal): %d mm", (
int)ret);
810#if defined(__ANDROID__)
811double BasePlatform::GetDisplayDPmm() {
return getAndroidDPmm(); }
814double BasePlatform::GetDisplayDPmm() {
815 if (
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
816 double r = getDisplaySize().x;
817 return r / GetDisplaySizeMM();
828 if (win) rv = (double)(win->ToDIP(100)) / 100.;
833unsigned int AbstractPlatform::GetSelectRadiusPix() {
834 return GetDisplayDPmm() *
835 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
842const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
843 0x00, 0x2b, 0xe1, 0x03, 0x18};
846bool GetMonitorSizeFromEDID(
const HKEY hDevRegKey,
int* WidthMm,
848 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
849 TCHAR valueName[NAME_SIZE];
852 DWORD edidsize =
sizeof(EDIDdata);
854 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
856 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
857 &AcutalValueNameLength, NULL, &dwType,
861 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T(
"EDID")))
864 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
865 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
873bool GetSizeForDevID(wxString& TargetDevID,
int* WidthMm,
int* HeightMm) {
875 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR,
883 if (NULL == devInfo)
return false;
887 for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
888 SP_DEVINFO_DATA devInfoData;
889 memset(&devInfoData, 0,
sizeof(devInfoData));
890 devInfoData.cbSize =
sizeof(devInfoData);
892 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
893 wchar_t Instance[80];
894 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
896 wxString instance(Instance);
897 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND)
continue;
899 HKEY hDevRegKey = SetupDiOpenDevRegKey(
900 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
902 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
continue;
904 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
906 RegCloseKey(hDevRegKey);
909 SetupDiDestroyDeviceInfoList(devInfo);
913bool AbstractPlatform::GetWindowsMonitorSize(
int* width,
int* height) {
914 bool bFoundDevice =
true;
916 if (m_monitorWidth < 10) {
926 bFoundDevice =
false;
927 while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice) {
928 DISPLAY_DEVICE ddMon;
929 ZeroMemory(&ddMon,
sizeof(ddMon));
930 ddMon.cb =
sizeof(ddMon);
933 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
935 if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
936 !(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
937 DeviceID = wxString(ddMon.DeviceID, wxConvUTF8);
938 DeviceID = DeviceID.Mid(8);
939 DeviceID = DeviceID.Mid(0, DeviceID.Find(
'\\'));
941 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
945 ZeroMemory(&ddMon,
sizeof(ddMon));
946 ddMon.cb =
sizeof(ddMon);
949 ZeroMemory(&dd,
sizeof(dd));
953 m_monitorWidth = WidthMm;
954 m_monitorHeight = HeightMm;
957 if (width) *width = m_monitorWidth;
958 if (height) *height = m_monitorHeight;
966 double size = w->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
967#if wxCHECK_VERSION(3, 1, 2)
970 size *=
static_cast<double>(w->ToDIP(100)) / 100.;
974 double pixel_per_mm = wxGetDisplaySize().x / GetDisplaySizeMM();
975 size = std::max(size, 7.0 * pixel_per_mm);
977 return std::round(size);
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.
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.