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 "base_platform.h"
60#include "ocpn_utils.h"
61#include "ocpn_plugin.h"
65#include "androidUTIL.h"
73static const char PATH_SEP =
';';
75static const char PATH_SEP =
':';
78static const char*
const DEFAULT_XDG_DATA_DIRS =
79 "~/.local/share:/usr/local/share:/usr/share";
81void appendOSDirSlash(wxString* pString);
83extern wxString g_winPluginDir;
85extern bool g_bportable;
87extern float g_selection_radius_mm;
88extern float g_selection_radius_touch_mm;
90extern wxLog* g_logger;
99extern bool m_bdisableWindowsDisplayEnum;
102static bool checkIfFlatpacked() {
104 if (!wxGetEnv(
"FLATPAK_ID", &
id)) {
107 return id ==
"org.opencpn.OpenCPN";
110static wxString ExpandPaths(wxString paths,
BasePlatform* platform);
112static wxString GetLinuxDataPath() {
114 if (wxGetEnv(
"XDG_DATA_DIRS", &dirs)) {
115 dirs = wxString(
"~/.local/share:") + dirs;
117 dirs = DEFAULT_XDG_DATA_DIRS;
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);
126 if (!dir.EndsWith(
"/opencpn/plugins")) {
127 dir +=
"/opencpn/plugins";
129 s += dir + (tokens.HasMoreTokens() ?
";" :
"");
134static wxString ExpandPaths(wxString paths,
BasePlatform* platform) {
135 wxStringTokenizer tokens(paths,
';');
137 while (tokens.HasMoreTokens()) {
138 wxFileName filename(tokens.GetNextToken());
139 filename.Normalize();
140 s += platform->NormalizePath(filename.GetFullPath());
141 if (tokens.HasMoreTokens()) {
149BasePlatform::BasePlatform() {
150 m_isFlatpacked = checkIfFlatpacked();
152 DetectOSDetail(m_osDetail);
160wxStandardPaths& BasePlatform::GetStdPaths() {
162 return wxStandardPaths::Get();
164 return *
dynamic_cast<wxStandardPaths*
>(
165 &(wxTheApp->GetTraits())->GetStandardPaths());
169wxString BasePlatform::NormalizePath(
const wxString& full_path) {
173 wxString path(full_path);
176 if (f.MakeRelativeTo(GetPrivateDataDir())) {
177 path = f.GetFullPath();
183wxString& BasePlatform::GetHomeDir() {
184 if (m_homeDir.IsEmpty()) {
186 wxStandardPaths& std_path = GetStdPaths();
191 std_path.SetInstallPrefix(wxString(PREFIX, wxConvUTF8));
199 m_homeDir = std_path.GetUserConfigDir();
203 m_homeDir = androidGetHomeDir();
207 wxFileName path(GetExePath());
208 m_homeDir = path.GetPath();
212 appendOSDirSlash(&m_homeDir);
213 m_homeDir.Append(_T(
"opencpn"));
216 appendOSDirSlash(&m_homeDir);
222wxString& BasePlatform::GetExePath() {
223 if (m_exePath.IsEmpty()) {
224 wxStandardPaths& std_path = GetStdPaths();
225 m_exePath = std_path.GetExecutablePath();
231wxString* BasePlatform::GetSharedDataDirPtr() {
232 if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
236wxString* BasePlatform::GetPrivateDataDirPtr() {
237 if (m_PrivateDataDir.IsEmpty()) GetPrivateDataDir();
238 return &m_PrivateDataDir;
241wxString& BasePlatform::GetSharedDataDir() {
242 if (m_SData_Dir.IsEmpty()) {
253 wxStandardPaths& std_path = GetStdPaths();
254 m_SData_Dir = std_path.GetDataDir();
255 appendOSDirSlash(&m_SData_Dir);
258 m_SData_Dir = androidGetSharedDir();
261 if (g_bportable) m_SData_Dir = GetHomeDir();
267wxString GetPluginDataDir(
const char* plugin_name) {
268 static const wxString sep = wxFileName::GetPathSeparator();
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);
277 if (!tryDir.Open(tryDirName.GetFullPath()))
continue;
279 bool more = tryDir.GetFirst(&next);
281 if (next == plugin_name) {
282 next = next.Prepend(tryDirName.GetFullPath() + sep);
283 wxLogMessage(_T(
"PlugInManager: using data dir: %s"), next);
286 more = tryDir.GetNext(&next);
290 wxLogMessage(_T(
"Warning: no data directory found, using \"\""));
294wxString& BasePlatform::GetPrivateDataDir() {
295 if (m_PrivateDataDir.IsEmpty()) {
297 wxStandardPaths& std_path = GetStdPaths();
303 std::string config_home;
304 if (getenv(
"XDG_CONFIG_HOME")) {
305 config_home = getenv(
"XDG_CONFIG_HOME");
307 config_home = getenv(
"HOME");
308 config_home +=
"/.var/app/org.opencpn.OpenCPN/config";
310 m_PrivateDataDir = config_home +
"/opencpn";
312#elif defined __WXOSX__
314 std_path.GetUserConfigDir();
315 appendOSDirSlash(&m_PrivateDataDir);
316 m_PrivateDataDir.Append(_T(
"opencpn"));
318 m_PrivateDataDir = std_path.GetUserDataDir();
322 m_PrivateDataDir = GetHomeDir();
323 if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
324 m_PrivateDataDir.RemoveLast();
328 m_PrivateDataDir = androidGetPrivateDir();
331 return m_PrivateDataDir;
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());
343 return fn.GetFullPath();
345 wxString winPluginDir;
348 winPluginDir = (GetHomeDir() + _T(
"plugins"));
349 if (ocpn::exists(winPluginDir.ToStdString())) {
350 wxLogMessage(
"Using portable plugin dir: %s", winPluginDir);
355 bool ok = wxGetEnv(_T(
"LOCALAPPDATA"), &winPluginDir);
357 wxLogMessage(
"winPluginDir: Cannot lookup LOCALAPPDATA");
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());
369 ok = wxGetEnv(_T(
"APPDATA"), &winPluginDir);
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());
379 wxLogMessage(
"winPluginDir: using %s", winPluginDir.mb_str().data());
384 winPluginDir = GetHomeDir();
386 wxFileName path(winPluginDir);
388 winPluginDir = path.GetFullPath() +
"\\opencpn\\plugins";
389 wxLogMessage(
"Using private plugin dir: %s", winPluginDir);
394 if (m_PluginsDir.IsEmpty()) {
395 wxStandardPaths& std_path = GetStdPaths();
398 m_PluginsDir = std_path.GetPluginsDir();
401 m_PluginsDir += _T(
"\\plugins");
404 m_PluginsDir = GetHomeDir();
405 m_PluginsDir += _T(
"plugins");
410 wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
411 fdir.RemoveLastDir();
412 m_PluginsDir = fdir.GetPath();
418wxString* BasePlatform::GetPluginDirPtr() {
420 return &m_PluginsDir;
423bool BasePlatform::isPlatformCapable(
int flag) {
427 if (flag == PLATFORM_CAP_PLUGINS) {
429 wxString tsdk(android_plat_spc.msdk);
430 if (tsdk.ToLong(&platver)) {
431 if (platver >= 11)
return true;
433 }
else if (flag == PLATFORM_CAP_FASTPAN) {
435 wxString tsdk(android_plat_spc.msdk);
436 if (tsdk.ToLong(&platver)) {
437 if (platver >= 14)
return true;
445void appendOSDirSlash(wxString* pString) {
446 wxChar sep = wxFileName::GetPathSeparator();
447 if (pString->Last() != sep) pString->Append(sep);
450wxString BasePlatform::GetWritableDocumentsDir() {
454 dir = androidGetExtStorageDir();
456 wxStandardPaths& std_path = GetStdPaths();
457 dir = std_path.GetDocumentsDir();
463 if (!detail)
return false;
466 detail->osd_name = std::string(PKG_TARGET);
467 detail->osd_version = std::string(PKG_TARGET_VERSION);
471 if (wxFileExists(_T(
"/etc/os-release"))) {
472 wxTextFile release_file(_T(
"/etc/os-release"));
473 if (release_file.Open()) {
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);
493 val = str.AfterFirst(
'=');
497 detail->osd_names_like = ocpn::split(val.mb_str(),
" ");
502 release_file.Close();
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()) {
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());
519 upstream_release_file.Close();
527 detail->osd_arch = std::string(
"x86_64");
530 wxPlatformInfo platformInfo = wxPlatformInfo::Get();
531 wxArchitecture arch = platformInfo.GetArchitecture();
532 if (arch == wxARCH_32) detail->osd_arch = std::string(
"i386");
538 detail->osd_arch = std::string(
"arm64");
540 detail->osd_arch = std::string(
"armhf");
545 detail->osd_arch = std::string(
"arm64");
546 if (arch == wxARCH_32) detail->osd_arch = std::string(
"armhf");
552wxString& BasePlatform::GetConfigFileName() {
553 if (m_config_file_name.IsEmpty()) {
555 wxStandardPaths& std_path = GetStdPaths();
558 m_config_file_name =
"opencpn.ini";
559 m_config_file_name.Prepend(GetHomeDir());
561#elif defined __WXOSX__
563 std_path.GetUserConfigDir();
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");
569 m_config_file_name = GetPrivateDataDir();
570 m_config_file_name.Append(
"/opencpn.conf");
573 m_config_file_name = std_path.GetUserDataDir();
574 appendOSDirSlash(&m_config_file_name);
575 m_config_file_name.Append(
"opencpn.conf");
579 m_config_file_name = GetHomeDir();
581 m_config_file_name +=
"opencpn.ini";
582#elif defined __WXOSX__
583 m_config_file_name +=
"opencpn.ini";
585 m_config_file_name +=
"opencpn.conf";
590 m_config_file_name = androidGetPrivateDir();
591 appendOSDirSlash(&m_config_file_name);
592 m_config_file_name +=
"opencpn.conf";
595 return m_config_file_name;
598bool BasePlatform::InitializeLogFile(
void) {
600 mlog_file = GetPrivateDataDir();
601 appendOSDirSlash(&mlog_file);
605 wxFileName LibPref(mlog_file);
606 LibPref.RemoveLastDir();
607 LibPref.RemoveLastDir();
609 mlog_file = LibPref.GetFullPath();
610 appendOSDirSlash(&mlog_file);
612 mlog_file.Append(_T(
"Logs/"));
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"));
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"));
634 mlog_file.Append(_T(
"opencpn.log"));
635 wxString logit = mlog_file;
638 wxCharBuffer abuf = mlog_file.ToUTF8();
639 qDebug() <<
"logfile " << abuf.data();
643 if (::wxFileExists(mlog_file)) {
644 if (wxFileName::GetSize(mlog_file) > 1000000) {
645 wxString oldlog = mlog_file;
646 oldlog.Append(_T(
".log"));
649 large_log_message = (_T(
"Old log will be moved to opencpn.log.log"));
650 ::wxRenameFile(mlog_file, oldlog);
654 if (::wxFileExists(mlog_file)) {
657 ::wxRemoveFile(mlog_file);
660 if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
663 wxLog::SetActiveTarget(
new wxLogStderr);
664 wxLog::SetTimestamp(
"");
665 wxLog::SetLogLevel(wxLOG_Warning);
667 g_logger =
new OcpnLog(mlog_file.mb_str());
668 m_Oldlogger = wxLog::SetActiveTarget(g_logger);
674void BasePlatform::CloseLogFile(
void) {
676 wxLog::SetActiveTarget(m_Oldlogger);
683 wxString sep = wxFileName::GetPathSeparator();
684 wxString ret = GetPrivateDataDir() + sep + _T(
"plugins");
688 if (m_pluginDataPath !=
"") {
689 return m_pluginDataPath;
693 wxString pluginDir = GetPrivateDataDir() +
"/plugins";
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) {
703 }
else if (osSystemId & wxOS_MAC) {
704 dirs =
"/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
706 "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
710 m_pluginDataPath = ExpandPaths(dirs,
this);
711 if (m_pluginDataPath !=
"") {
712 m_pluginDataPath +=
";";
715 if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
716 m_pluginDataPath.RemoveLast();
718 wxLogMessage(
"Using plugin data path: %s", m_pluginDataPath.mb_str().data());
719 return m_pluginDataPath;
724void BasePlatform::ShowBusySpinner() { androidShowBusyIcon(); }
726void BasePlatform::ShowBusySpinner() { }
728void BasePlatform::ShowBusySpinner() { ::wxBeginBusyCursor(); }
732void BasePlatform::HideBusySpinner() { androidHideBusyIcon(); }
734void BasePlatform::HideBusySpinner() { }
736void BasePlatform::HideBusySpinner() { ::wxEndBusyCursor(); }
742wxSize BasePlatform::getDisplaySize() {
return wxSize(); }
744#elif defined(__ANDROID__)
745wxSize BasePlatform::getDisplaySize() {
return getAndroidDisplayDimensions(); }
748wxSize BasePlatform::getDisplaySize() {
749 if (m_displaySize.x < 10)
750 m_displaySize = ::wxGetDisplaySize();
751 return m_displaySize;
758double BasePlatform::GetDisplaySizeMM() {
return 1.0; }
761double BasePlatform::GetDisplaySizeMM() {
763 if (m_displaySizeMMOverride > 0)
return m_displaySizeMMOverride;
765 if (m_displaySizeMM.x < 1) m_displaySizeMM = wxGetDisplaySizeMM();
767 double ret = m_displaySizeMM.GetWidth();
772 if (!m_bdisableWindowsDisplayEnum) {
773 if (GetWindowsMonitorSize(&w, &h) && (w > 100)) {
774 m_displaySizeMM == wxSize(w, h);
777 m_bdisableWindowsDisplayEnum =
true;
782 ret = GetMacMonitorSize();
786 ret = GetAndroidDisplaySize();
789 wxLogDebug(
"Detected display size (horizontal): %d mm", (
int)ret);
796double BasePlatform::GetDisplayDPmm() {
return 1.0; }
798#elif defined(__ANDROID__)
799double BasePlatform::GetDisplayDPmm() {
return getAndroidDPmm(); }
802double BasePlatform::GetDisplayDPmm() {
803 double r = getDisplaySize().x;
804 return r / GetDisplaySizeMM();
809double BasePlatform::GetDisplayDIPMult(wxWindow *win) {
813 rv = (double)(win->ToDIP(100))/100.;
818unsigned int BasePlatform::GetSelectRadiusPix() {
819 return GetDisplayDPmm() *
820 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
827const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
828 0x00, 0x2b, 0xe1, 0x03, 0x18};
831bool GetMonitorSizeFromEDID(
const HKEY hDevRegKey,
int *WidthMm,
833 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
834 TCHAR valueName[NAME_SIZE];
837 DWORD edidsize =
sizeof(EDIDdata);
839 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
841 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
842 &AcutalValueNameLength, NULL, &dwType,
846 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T(
"EDID")))
849 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
850 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
858bool GetSizeForDevID(wxString &TargetDevID,
int *WidthMm,
int *HeightMm) {
860 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR,
868 if (NULL == devInfo)
return false;
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);
877 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
878 wchar_t Instance[80];
879 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
881 wxString instance(Instance);
882 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND)
continue;
884 HKEY hDevRegKey = SetupDiOpenDevRegKey(
885 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
887 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
continue;
889 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
891 RegCloseKey(hDevRegKey);
894 SetupDiDestroyDeviceInfoList(devInfo);
898bool BasePlatform::GetWindowsMonitorSize(
int *width,
int *height) {
899 bool bFoundDevice =
true;
901 if (m_monitorWidth < 10) {
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);
918 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
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(
'\\'));
926 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
930 ZeroMemory(&ddMon,
sizeof(ddMon));
931 ddMon.cb =
sizeof(ddMon);
934 ZeroMemory(&dd,
sizeof(dd));
938 m_monitorWidth = WidthMm;
939 m_monitorHeight = HeightMm;
942 if (width) *width = m_monitorWidth;
943 if (height) *height = m_monitorHeight;