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"
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();
277 lic = androidGetSupplementalLicense();
282wxString GetPluginDataDir(
const char* plugin_name) {
283 static const wxString sep = wxFileName::GetPathSeparator();
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);
292 if (!tryDir.Open(tryDirName.GetFullPath()))
continue;
294 bool more = tryDir.GetFirst(&next);
296 if (next == plugin_name) {
297 next = next.Prepend(tryDirName.GetFullPath() + sep);
298 wxLogMessage(_T(
"PlugInManager: using data dir: %s"), next);
301 more = tryDir.GetNext(&next);
305 wxLogMessage(_T(
"Warning: no data directory found, using \"\""));
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;
323 if (m_PrivateDataDir.IsEmpty()) {
325 wxStandardPaths& std_path = GetStdPaths();
331 std::string config_home;
332 if (getenv(
"XDG_CONFIG_HOME")) {
333 config_home = getenv(
"XDG_CONFIG_HOME");
335 config_home = getenv(
"HOME");
336 config_home +=
"/.var/app/org.opencpn.OpenCPN/config";
338 m_PrivateDataDir = config_home +
"/opencpn";
340#elif defined __WXOSX__
342 std_path.GetUserConfigDir();
343 appendOSDirSlash(&m_PrivateDataDir);
344 m_PrivateDataDir.Append(_T(
"opencpn"));
346 m_PrivateDataDir = std_path.GetUserDataDir();
349 if (g_bportable) m_PrivateDataDir = GetHomeDir();
350 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
351 m_PrivateDataDir.RemoveLast();
354 m_PrivateDataDir = androidGetPrivateDir();
357 return m_PrivateDataDir;
362 wxLogMessage(
"winPluginDir: Using value from ini file.");
364 if (!fn.DirExists()) {
365 wxLogWarning(
"Plugin dir %s does not exist",
366 fn.GetFullPath().mb_str().data());
369 return fn.GetFullPath();
371 wxString winPluginDir;
374 winPluginDir = (GetHomeDir() + _T(
"plugins"));
376 wxLogMessage(
"Using portable plugin dir: %s", winPluginDir);
381 bool ok = wxGetEnv(_T(
"LOCALAPPDATA"), &winPluginDir);
383 wxLogMessage(
"winPluginDir: Cannot lookup LOCALAPPDATA");
385 std::string path(wxGetHomeDir().ToStdString());
386 path +=
"\\AppData\\Local";
388 winPluginDir = wxString(path.c_str());
389 wxLogMessage(
"winPluginDir: using %s", winPluginDir.mb_str().data());
395 ok = wxGetEnv(_T(
"APPDATA"), &winPluginDir);
399 wxLogMessage(
"winPluginDir: Cannot lookup APPDATA");
400 std::string path(wxGetHomeDir().ToStdString());
401 path +=
"\\AppData\\Roaming";
403 winPluginDir = wxString(path.c_str());
405 wxLogMessage(
"winPluginDir: using %s", winPluginDir.mb_str().data());
410 winPluginDir = GetHomeDir();
412 wxFileName path(winPluginDir);
414 winPluginDir = path.GetFullPath() +
"\\opencpn\\plugins";
415 wxLogMessage(
"Using private plugin dir: %s", winPluginDir);
420 if (m_PluginsDir.IsEmpty()) {
421 wxStandardPaths& std_path = GetStdPaths();
424 m_PluginsDir = std_path.GetPluginsDir();
427 m_PluginsDir += _T(
"\\plugins");
430 m_PluginsDir = GetHomeDir();
431 m_PluginsDir += _T(
"plugins");
436 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
437 fdir.RemoveLastDir();
438 m_PluginsDir = fdir.GetPath();
444wxString* AbstractPlatform::GetPluginDirPtr() {
446 return &m_PluginsDir;
449bool AbstractPlatform::isPlatformCapable(
int flag) {
453 if (flag == PLATFORM_CAP_PLUGINS) {
455 wxString tsdk(android_plat_spc.msdk);
456 if (tsdk.ToLong(&platver)) {
457 if (platver >= 11)
return true;
459 }
else if (flag == PLATFORM_CAP_FASTPAN) {
461 wxString tsdk(android_plat_spc.msdk);
462 if (tsdk.ToLong(&platver)) {
463 if (platver >= 14)
return true;
471void appendOSDirSlash(wxString* pString) {
472 wxChar sep = wxFileName::GetPathSeparator();
473 if (pString->Last() != sep) pString->Append(sep);
476wxString AbstractPlatform::GetWritableDocumentsDir() {
480 dir = androidGetExtStorageDir();
482 wxStandardPaths& std_path = GetStdPaths();
483 dir = std_path.GetDocumentsDir();
488bool AbstractPlatform::DetectOSDetail(
OCPN_OSDetail* detail) {
489 if (!detail)
return false;
492 detail->osd_name = std::string(PKG_TARGET);
493 detail->osd_version = std::string(PKG_TARGET_VERSION);
497 if (wxFileExists(_T(
"/etc/os-release"))) {
498 wxTextFile release_file(_T(
"/etc/os-release"));
499 if (release_file.Open()) {
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);
519 val = str.AfterFirst(
'=');
523 detail->osd_names_like =
ocpn::split(val.mb_str(),
" ");
528 release_file.Close();
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()) {
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());
545 upstream_release_file.Close();
553 detail->osd_arch = std::string(
"x86_64");
556 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
557 wxArchitecture arch = platformInfo.GetArchitecture();
558 if (arch == wxARCH_32) detail->osd_arch = std::string(
"i386");
564 detail->osd_arch = std::string(
"arm64");
566 detail->osd_arch = std::string(
"armhf");
571 detail->osd_arch = std::string(
"arm64");
572 if (arch == wxARCH_32) detail->osd_arch = std::string(
"armhf");
576 if (IsAppleSilicon() == 1) {
577 if (ProcessIsTranslated() != 1) {
578 detail->osd_arch = std::string(
"arm64");
580 detail->osd_arch = std::string(
"x86_64");
583 detail->osd_arch = std::string(
"x86_64");
590wxString& AbstractPlatform::GetConfigFileName() {
591 if (m_config_file_name.IsEmpty()) {
593 wxStandardPaths& std_path = GetStdPaths();
596 m_config_file_name =
"opencpn.ini";
597 m_config_file_name.Prepend(GetHomeDir());
599#elif defined __WXOSX__
601 std_path.GetUserConfigDir();
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");
608 m_config_file_name.Append(
"/opencpn.conf");
611 m_config_file_name = std_path.GetUserDataDir();
612 appendOSDirSlash(&m_config_file_name);
613 m_config_file_name.Append(
"opencpn.conf");
617 m_config_file_name = GetHomeDir();
619 m_config_file_name +=
"opencpn.ini";
620#elif defined __WXOSX__
621 m_config_file_name +=
"opencpn.ini";
623 m_config_file_name +=
"opencpn.conf";
628 m_config_file_name = androidGetPrivateDir();
629 appendOSDirSlash(&m_config_file_name);
630 m_config_file_name +=
"opencpn.conf";
632 if (!g_configdir.empty()) {
633 m_config_file_name = g_configdir;
634 m_config_file_name.Append(
"/opencpn.conf");
637 return m_config_file_name;
640bool BasePlatform::InitializeLogFile(
void) {
643 appendOSDirSlash(&mlog_file);
647 wxFileName LibPref(mlog_file);
648 LibPref.RemoveLastDir();
649 LibPref.RemoveLastDir();
651 mlog_file = LibPref.GetFullPath();
652 appendOSDirSlash(&mlog_file);
654 mlog_file.Append(_T(
"Logs/"));
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"));
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"));
676 mlog_file.Append(_T(
"opencpn.log"));
677 wxString logit = mlog_file;
680 wxCharBuffer abuf = mlog_file.ToUTF8();
681 qDebug() <<
"logfile " << abuf.data();
685 if (::wxFileExists(mlog_file)) {
686 if (wxFileName::GetSize(mlog_file) > 1000000) {
687 wxString oldlog = mlog_file;
688 oldlog.Append(_T(
".log"));
691 large_log_message = (_T(
"Old log will be moved to opencpn.log.log"));
692 ::wxRenameFile(mlog_file, oldlog);
696 if (::wxFileExists(mlog_file)) {
699 ::wxRemoveFile(mlog_file);
703 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
705 auto logger =
new OcpnLog(mlog_file.mb_str());
709 m_old_logger = wxLog::SetActiveTarget(logger);
714void AbstractPlatform::CloseLogFile(
void) {
716 delete wxLog::SetActiveTarget(m_old_logger);
717 m_old_logger =
nullptr;
723 wxString sep = wxFileName::GetPathSeparator();
728 if (m_pluginDataPath !=
"") {
729 return m_pluginDataPath;
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) {
743 }
else if (osSystemId & wxOS_MAC) {
744 dirs =
"/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
746 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
750 m_pluginDataPath = ExpandPaths(dirs,
this);
751 if (m_pluginDataPath !=
"") {
752 m_pluginDataPath +=
";";
755 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
756 m_pluginDataPath.RemoveLast();
758 wxLogMessage(
"Using plugin data path: %s", m_pluginDataPath.mb_str().data());
759 return m_pluginDataPath;
763void AbstractPlatform::ShowBusySpinner() {
765 androidShowBusyIcon();
770void AbstractPlatform::ShowBusySpinner() {
772 ::wxBeginBusyCursor();
779void AbstractPlatform::HideBusySpinner() {
781 androidHideBusyIcon();
786void AbstractPlatform::HideBusySpinner() {
796#if defined(__ANDROID__)
797wxSize BasePlatform::getDisplaySize() {
return getAndroidDisplayDimensions(); }
800wxSize BasePlatform::getDisplaySize() {
return wxSize(0, 0); }
804double BasePlatform::GetDisplaySizeMM() {
805 if (m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
806 return m_displaySizeMMOverride[0];
811 ret = GetAndroidDisplaySize();
814 wxLogDebug(
"Detected display size (horizontal): %d mm", (
int)ret);
818#if defined(__ANDROID__)
819double BasePlatform::GetDisplayDPmm() {
return getAndroidDPmm(); }
822double BasePlatform::GetDisplayDPmm() {
823 if (
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
824 double r = getDisplaySize().x;
825 return r / GetDisplaySizeMM();
836 if (win) rv = (double)(win->ToDIP(100)) / 100.;
841unsigned int AbstractPlatform::GetSelectRadiusPix() {
842 return GetDisplayDPmm() *
843 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
850const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
851 0x00, 0x2b, 0xe1, 0x03, 0x18};
854bool GetMonitorSizeFromEDID(
const HKEY hDevRegKey,
int* WidthMm,
856 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
857 TCHAR valueName[NAME_SIZE];
860 DWORD edidsize =
sizeof(EDIDdata);
862 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
864 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
865 &AcutalValueNameLength, NULL, &dwType,
869 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T(
"EDID")))
872 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
873 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
881bool GetSizeForDevID(wxString& TargetDevID,
int* WidthMm,
int* HeightMm) {
883 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR,
891 if (NULL == devInfo)
return false;
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);
900 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
901 wchar_t Instance[80];
902 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
904 wxString instance(Instance);
905 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND)
continue;
907 HKEY hDevRegKey = SetupDiOpenDevRegKey(
908 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
910 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
continue;
912 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
914 RegCloseKey(hDevRegKey);
917 SetupDiDestroyDeviceInfoList(devInfo);
921bool AbstractPlatform::GetWindowsMonitorSize(
int* width,
int* height) {
922 bool bFoundDevice =
true;
924 if (m_monitorWidth < 10) {
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);
941 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
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(
'\\'));
949 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
953 ZeroMemory(&ddMon,
sizeof(ddMon));
954 ddMon.cb =
sizeof(ddMon);
957 ZeroMemory(&dd,
sizeof(dd));
961 m_monitorWidth = WidthMm;
962 m_monitorHeight = HeightMm;
965 if (width) *width = m_monitorWidth;
966 if (height) *height = m_monitorHeight;
974 double size = w->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
975#if wxCHECK_VERSION(3, 1, 2)
978 size *=
static_cast<double>(w->ToDIP(100)) / 100.;
982 double pixel_per_mm = wxGetDisplaySize().x / GetDisplaySizeMM();
983 size = std::max(size, 7.0 * pixel_per_mm);
985 return std::round(size);
Customized logger class appending to a file providing:
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.