26 #include <wx/wxprec.h>
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"
61 #include "model/logger.h"
62 #include "model/ocpn_utils.h"
67 #include "androidUTIL.h"
71 #include "model/macutils.h"
75 static const char PATH_SEP =
';';
77 static const char PATH_SEP =
':';
80 bool AbstractPlatform::m_isBusy =
false;
82 static const char*
const DEFAULT_XDG_DATA_DIRS =
83 "~/.local/share:/usr/local/share:/usr/share";
85 void appendOSDirSlash(wxString* pString);
93 static bool checkIfFlatpacked() {
95 if (!wxGetEnv(
"FLATPAK_ID", &
id)) {
98 return id ==
"org.opencpn.OpenCPN";
103 static 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()) {
140 BasePlatform::BasePlatform() {
142 m_isFlatpacked = checkIfFlatpacked();
144 DetectOSDetail(m_osDetail);
153 BasePlatform::~BasePlatform() {
155 delete wxLog::SetActiveTarget(
new wxLogStderr());
162 wxStandardPaths& AbstractPlatform::GetStdPaths() {
164 return wxStandardPaths::Get();
166 return *
dynamic_cast<wxStandardPaths*
>(
167 &(wxTheApp->GetTraits())->GetStandardPaths());
171 wxString AbstractPlatform::NormalizePath(
const wxString& full_path) {
175 wxString path(full_path);
179 path = f.GetFullPath();
185 wxString& 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);
224 wxString& AbstractPlatform::GetExePath() {
225 if (m_exePath.IsEmpty()) {
226 wxStandardPaths& std_path = GetStdPaths();
227 m_exePath = std_path.GetExecutablePath();
233 wxString* AbstractPlatform::GetSharedDataDirPtr() {
234 if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
240 return &m_PrivateDataDir;
243 wxString& 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();
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();
317 #elif defined FLATPAK
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();
431 wxString* AbstractPlatform::GetPluginDirPtr() {
433 return &m_PluginsDir;
436 bool 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;
458 void appendOSDirSlash(wxString* pString) {
459 wxChar sep = wxFileName::GetPathSeparator();
460 if (pString->Last() != sep) pString->Append(sep);
463 wxString AbstractPlatform::GetWritableDocumentsDir() {
467 dir = androidGetExtStorageDir();
469 wxStandardPaths& std_path = GetStdPaths();
470 dir = std_path.GetDocumentsDir();
475 bool 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");
577 wxString& 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");
593 #elif defined FLATPAK
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;
627 bool 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);
701 void 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;
750 void AbstractPlatform::ShowBusySpinner() {
752 androidShowBusyIcon();
757 void AbstractPlatform::ShowBusySpinner() {
759 ::wxBeginBusyCursor();
766 void AbstractPlatform::HideBusySpinner() {
768 androidHideBusyIcon();
773 void AbstractPlatform::HideBusySpinner() {
785 #if defined(__ANDROID__)
786 wxSize BasePlatform::getDisplaySize() {
return getAndroidDisplayDimensions(); }
789 wxSize BasePlatform::getDisplaySize() {
795 double BasePlatform::GetDisplaySizeMM() {
796 if(m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
797 return m_displaySizeMMOverride[0];
802 ret = GetAndroidDisplaySize();
805 wxLogDebug(
"Detected display size (horizontal): %d mm", (
int)ret);
809 #if defined(__ANDROID__)
810 double BasePlatform::GetDisplayDPmm() {
return getAndroidDPmm(); }
813 double BasePlatform::GetDisplayDPmm() {
814 if (
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
815 double r = getDisplaySize().x;
816 return r / GetDisplaySizeMM();
825 double AbstractPlatform::GetDisplayDIPMult(wxWindow *win) {
829 rv = (double)(win->ToDIP(100))/100.;
834 unsigned int AbstractPlatform::GetSelectRadiusPix() {
835 return GetDisplayDPmm() *
836 (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
841 #define NAME_SIZE 128
843 const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
844 0x00, 0x2b, 0xe1, 0x03, 0x18};
847 bool GetMonitorSizeFromEDID(
const HKEY hDevRegKey,
int *WidthMm,
849 DWORD dwType, AcutalValueNameLength = NAME_SIZE;
850 TCHAR valueName[NAME_SIZE];
853 DWORD edidsize =
sizeof(EDIDdata);
855 for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
857 retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
858 &AcutalValueNameLength, NULL, &dwType,
862 if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T(
"EDID")))
865 *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
866 *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
874 bool GetSizeForDevID(wxString &TargetDevID,
int *WidthMm,
int *HeightMm) {
876 SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR,
884 if (NULL == devInfo)
return false;
888 for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
889 SP_DEVINFO_DATA devInfoData;
890 memset(&devInfoData, 0,
sizeof(devInfoData));
891 devInfoData.cbSize =
sizeof(devInfoData);
893 if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
894 wchar_t Instance[80];
895 SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
897 wxString instance(Instance);
898 if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND)
continue;
900 HKEY hDevRegKey = SetupDiOpenDevRegKey(
901 devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
903 if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
continue;
905 bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
907 RegCloseKey(hDevRegKey);
910 SetupDiDestroyDeviceInfoList(devInfo);
914 bool AbstractPlatform::GetWindowsMonitorSize(
int *width,
int *height) {
915 bool bFoundDevice =
true;
917 if (m_monitorWidth < 10) {
927 bFoundDevice =
false;
928 while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice) {
929 DISPLAY_DEVICE ddMon;
930 ZeroMemory(&ddMon,
sizeof(ddMon));
931 ddMon.cb =
sizeof(ddMon);
934 while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
936 if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
937 !(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
938 DeviceID = wxString(ddMon.DeviceID, wxConvUTF8);
939 DeviceID = DeviceID.Mid(8);
940 DeviceID = DeviceID.Mid(0, DeviceID.Find(
'\\'));
942 bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
946 ZeroMemory(&ddMon,
sizeof(ddMon));
947 ddMon.cb =
sizeof(ddMon);
950 ZeroMemory(&dd,
sizeof(dd));
954 m_monitorWidth = WidthMm;
955 m_monitorHeight = HeightMm;
958 if (width) *width = m_monitorWidth;
959 if (height) *height = m_monitorHeight;
Global variables reflecting command line options and arguments.
DECL_EXP wxString GetPluginDataDir(const char *plugin_name)
Return the plugin data directory for a given directory name.