OpenCPN Partial API docs
base_platform.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: OpenCPN Platform specific support utilities
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2015 by David S. Register *
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  * This program is distributed in the hope that it will be useful, *
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18  * GNU General Public License for more details. *
19  * *
20  * You should have received a copy of the GNU General Public License *
21  * along with this program; if not, write to the *
22  * Free Software Foundation, Inc., *
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24  **************************************************************************/
25 
26 #include <wx/wxprec.h>
27 
28 #include <cstdlib>
29 #include <string>
30 #include <vector>
31 
32 #ifdef __MINGW32__
33 #undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
34 #include <windows.h>
35 #endif
36 
37 #ifndef WX_PRECOMP
38 #include <wx/wx.h>
39 #endif // precompiled headers
40 
41 #ifdef __WXMSW__
42 #include <windows.h>
43 #include <winioctl.h>
44 #include <initguid.h>
45 #include "setupapi.h" // presently stored in opencpn/src
46 #endif
47 
48 #include <wx/app.h>
49 #include <wx/apptrait.h>
50 #include <wx/dir.h>
51 #include <wx/filename.h>
52 #include <wx/stdpaths.h>
53 #include <wx/textfile.h>
54 #include <wx/tokenzr.h>
55 
56 #include "config.h"
57 
58 #include "model/base_platform.h"
59 #include "model/cmdline.h"
60 #include "model/config_vars.h"
61 #include "model/logger.h"
62 #include "model/ocpn_utils.h"
63 #include "ocpn_plugin.h"
64 
65 #ifdef __ANDROID__
66 #include <QDebug>
67 #include "androidUTIL.h"
68 #endif
69 
70 #ifdef __WXOSX__
71 #include "model/macutils.h"
72 #endif
73 
74 #ifdef __WXMSW__
75 static const char PATH_SEP = ';';
76 #else
77 static const char PATH_SEP = ':';
78 #endif
79 
80 bool AbstractPlatform::m_isBusy = false;
81 
82 static const char* const DEFAULT_XDG_DATA_DIRS =
83  "~/.local/share:/usr/local/share:/usr/share";
84 
85 void appendOSDirSlash(wxString* pString);
86 
87 BasePlatform* g_BasePlatform;
88 
89 #ifdef __ANDROID__
90 PlatSpec android_plat_spc;
91 #endif
92 
93 static bool checkIfFlatpacked() {
94  wxString id;
95  if (!wxGetEnv("FLATPAK_ID", &id)) {
96  return false;
97  }
98  return id == "org.opencpn.OpenCPN";
99 }
100 
101 static wxString ExpandPaths(wxString paths, AbstractPlatform* platform);
102 
103 static wxString GetLinuxDataPath() {
104  wxString dirs;
105  if (wxGetEnv("XDG_DATA_DIRS", &dirs)) {
106  dirs = wxString("~/.local/share:") + dirs;
107  } else {
108  dirs = DEFAULT_XDG_DATA_DIRS;
109  }
110  wxString s;
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);
116  }
117  if (!dir.EndsWith("/opencpn/plugins")) {
118  dir += "/opencpn/plugins";
119  }
120  s += dir + (tokens.HasMoreTokens() ? ";" : "");
121  }
122  return s;
123 }
124 
125 static wxString ExpandPaths(wxString paths, AbstractPlatform* platform) {
126  wxStringTokenizer tokens(paths, ';');
127  wxString s = "";
128  while (tokens.HasMoreTokens()) {
129  wxFileName filename(tokens.GetNextToken());
130  filename.Normalize();
131  s += platform->NormalizePath(filename.GetFullPath());
132  if (tokens.HasMoreTokens()) {
133  s += ';';
134  }
135  }
136  return s;
137 }
138 
139 // OCPN Platform implementation
140 BasePlatform::BasePlatform() {
141  m_old_logger = 0;
142  m_isFlatpacked = checkIfFlatpacked();
143  m_osDetail = new OCPN_OSDetail;
144  DetectOSDetail(m_osDetail);
145 
146 #ifdef __ANDROID__
147  androidUtilInit();
148 #endif
149 
150  InitializeLogFile();
151 }
152 
153 BasePlatform::~BasePlatform() {
154  delete m_osDetail;
155  delete wxLog::SetActiveTarget(new wxLogStderr());
156 }
157 
158 //--------------------------------------------------------------------------
159 // Per-Platform file/directory support
160 //--------------------------------------------------------------------------
161 
162 wxStandardPaths& AbstractPlatform::GetStdPaths() {
163 #ifndef __ANDROID__
164  return wxStandardPaths::Get();
165 #else
166  return *dynamic_cast<wxStandardPaths*>(
167  &(wxTheApp->GetTraits())->GetStandardPaths());
168 #endif
169 }
170 
171 wxString AbstractPlatform::NormalizePath(const wxString& full_path) {
172  if (!g_bportable) {
173  return full_path;
174  } else {
175  wxString path(full_path);
176  wxFileName f(path);
177  // If not on another voulme etc. make the portable relative path
178  if (f.MakeRelativeTo(GetPrivateDataDir())) {
179  path = f.GetFullPath();
180  }
181  return path;
182  }
183 }
184 
185 wxString& AbstractPlatform::GetHomeDir() {
186  if (m_homeDir.IsEmpty()) {
187  // Establish a "home" location
188  wxStandardPaths& std_path = GetStdPaths();
189  // TODO Why is the following preferred? Will not compile with gcc...
190  // wxStandardPaths& std_path = wxApp::GetTraits()->GetStandardPaths();
191 
192 #ifdef __unix__
193  std_path.SetInstallPrefix(wxString(PREFIX, wxConvUTF8));
194 #endif
195 
196 #ifdef __WXMSW__
197  m_homeDir =
198  std_path
199  .GetConfigDir(); // on w98, produces "/windows/Application Data"
200 #else
201  m_homeDir = std_path.GetUserConfigDir();
202 #endif
203 
204 #ifdef __ANDROID__
205  m_homeDir = androidGetHomeDir();
206 #endif
207 
208  if (g_bportable) {
209  wxFileName path(GetExePath());
210  m_homeDir = path.GetPath();
211  }
212 
213 #ifdef __WXOSX__
214  appendOSDirSlash(&m_homeDir);
215  m_homeDir.Append(_T("opencpn"));
216 #endif
217 
218  appendOSDirSlash(&m_homeDir);
219  }
220 
221  return m_homeDir;
222 }
223 
224 wxString& AbstractPlatform::GetExePath() {
225  if (m_exePath.IsEmpty()) {
226  wxStandardPaths& std_path = GetStdPaths();
227  m_exePath = std_path.GetExecutablePath();
228  }
229 
230  return m_exePath;
231 }
232 
233 wxString* AbstractPlatform::GetSharedDataDirPtr() {
234  if (m_SData_Dir.IsEmpty()) GetSharedDataDir();
235  return &m_SData_Dir;
236 }
237 
239  if (m_PrivateDataDir.IsEmpty()) GetPrivateDataDir();
240  return &m_PrivateDataDir;
241 }
242 
243 wxString& AbstractPlatform::GetSharedDataDir() {
244  if (m_SData_Dir.IsEmpty()) {
245  // Establish a "shared data" location
246  /* From the wxWidgets documentation...
247  *
248  * wxStandardPaths::GetDataDir
249  * wxString GetDataDir() const
250  * Return the location of the applications global, i.e. not
251  * user-specific, data files. Unix: prefix/share/appname Windows: the
252  * directory where the executable file is located Mac:
253  * appname.app/Contents/SharedSupport bundle subdirectory
254  */
255  wxStandardPaths& std_path = GetStdPaths();
256  m_SData_Dir = std_path.GetDataDir();
257  appendOSDirSlash(&m_SData_Dir);
258 
259 #ifdef __ANDROID__
260  m_SData_Dir = androidGetSharedDir();
261 #endif
262 
263  if (g_bportable) m_SData_Dir = GetHomeDir();
264  }
265 
266  return m_SData_Dir;
267 }
268 
269 wxString GetPluginDataDir(const char* plugin_name) {
270  static const wxString sep = wxFileName::GetPathSeparator();
271 
272  wxString datadirs = g_BasePlatform->GetPluginDataPath();
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);
278  wxDir tryDir;
279  if (!tryDir.Open(tryDirName.GetFullPath())) continue;
280  wxString next;
281  bool more = tryDir.GetFirst(&next);
282  while (more) {
283  if (next == plugin_name) {
284  next = next.Prepend(tryDirName.GetFullPath() + sep);
285  wxLogMessage(_T("PlugInManager: using data dir: %s"), next);
286  return next;
287  }
288  more = tryDir.GetNext(&next);
289  }
290  tryDir.Close();
291  }
292  wxLogMessage(_T("Warning: no data directory found, using \"\""));
293  return "";
294 }
295 
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; // FIXME (leamas) normalize and trust
304  // g_configdir
305  }
306  return DefaultPrivateDataDir();
307 }
308 
310  if (m_PrivateDataDir.IsEmpty()) {
311  // Establish the prefix of the location of user specific data files
312  wxStandardPaths& std_path = GetStdPaths();
313 
314 #ifdef __WXMSW__
315  m_PrivateDataDir =
316  GetHomeDir(); // should be {Documents and Settings}\......
317 #elif defined FLATPAK
318  std::string config_home;
319  if (getenv("XDG_CONFIG_HOME")) {
320  config_home = getenv("XDG_CONFIG_HOME");
321  } else {
322  config_home = getenv("HOME");
323  config_home += "/.var/app/org.opencpn.OpenCPN/config";
324  }
325  m_PrivateDataDir = config_home + "/opencpn";
326 
327 #elif defined __WXOSX__
328  m_PrivateDataDir =
329  std_path.GetUserConfigDir(); // should be ~/Library/Preferences
330  appendOSDirSlash(&m_PrivateDataDir);
331  m_PrivateDataDir.Append(_T("opencpn"));
332 #else
333  m_PrivateDataDir = std_path.GetUserDataDir(); // should be ~/.opencpn
334 #endif
335 
336  if (g_bportable) m_PrivateDataDir = GetHomeDir();
337  if (m_PrivateDataDir.Last() == wxFileName::GetPathSeparator())
338  m_PrivateDataDir.RemoveLast();
339 
340 #ifdef __ANDROID__
341  m_PrivateDataDir = androidGetPrivateDir();
342 #endif
343  }
344  return m_PrivateDataDir;
345 }
346 
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());
354  }
355  fn.Normalize();
356  return fn.GetFullPath();
357  }
358  wxString winPluginDir;
359  // Portable case: plugins directory is in the .exe folder
360  if (g_bportable) {
361  winPluginDir = (GetHomeDir() + _T("plugins"));
362  if (ocpn::exists(winPluginDir.ToStdString())) {
363  wxLogMessage("Using portable plugin dir: %s", winPluginDir);
364  return winPluginDir;
365  }
366  }
367  // Standard case: c:\Users\%USERPROFILE%\AppData\Local
368  bool ok = wxGetEnv(_T("LOCALAPPDATA"), &winPluginDir);
369  if (!ok) {
370  wxLogMessage("winPluginDir: Cannot lookup LOCALAPPDATA");
371  // Without %LOCALAPPDATA%: Use default location if it exists.
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());
377  ok = true;
378  }
379  }
380  if (!ok) {
381  // Usually: c:\Users\%USERPROFILE%\AppData\Roaming
382  ok = wxGetEnv(_T("APPDATA"), &winPluginDir);
383  }
384  if (!ok) {
385  // Without %APPDATA%: Use default location if it exists.
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());
391  ok = true;
392  wxLogMessage("winPluginDir: using %s", winPluginDir.mb_str().data());
393  }
394  }
395  if (!ok) {
396  // {Documents and Settings}\.. on W7, else \ProgramData
397  winPluginDir = GetHomeDir();
398  }
399  wxFileName path(winPluginDir);
400  path.Normalize();
401  winPluginDir = path.GetFullPath() + "\\opencpn\\plugins";
402  wxLogMessage("Using private plugin dir: %s", winPluginDir);
403  return winPluginDir;
404 }
405 
407  if (m_PluginsDir.IsEmpty()) {
408  wxStandardPaths& std_path = GetStdPaths();
409 
410  // Get the PlugIns directory location
411  m_PluginsDir = std_path.GetPluginsDir(); // linux: {prefix}/lib/opencpn
412  // Mac: appname.app/Contents/PlugIns
413 #ifdef __WXMSW__
414  m_PluginsDir += _T("\\plugins"); // Windows: {exe dir}/plugins
415 #endif
416  if (g_bportable) {
417  m_PluginsDir = GetHomeDir();
418  m_PluginsDir += _T("plugins");
419  }
420 
421 #ifdef __ANDROID__
422  // something like: data/data/org.opencpn.opencpn
423  wxFileName fdir = wxFileName::DirName(std_path.GetUserConfigDir());
424  fdir.RemoveLastDir();
425  m_PluginsDir = fdir.GetPath();
426 #endif
427  }
428  return m_PluginsDir;
429 }
430 
431 wxString* AbstractPlatform::GetPluginDirPtr() {
432  if (m_PluginsDir.IsEmpty()) GetPluginDir();
433  return &m_PluginsDir;
434 }
435 
436 bool AbstractPlatform::isPlatformCapable(int flag) {
437 #ifndef __ANDROID__
438  return true;
439 #else
440  if (flag == PLATFORM_CAP_PLUGINS) {
441  long platver;
442  wxString tsdk(android_plat_spc.msdk);
443  if (tsdk.ToLong(&platver)) {
444  if (platver >= 11) return true;
445  }
446  } else if (flag == PLATFORM_CAP_FASTPAN) {
447  long platver;
448  wxString tsdk(android_plat_spc.msdk);
449  if (tsdk.ToLong(&platver)) {
450  if (platver >= 14) return true;
451  }
452  }
453 
454  return false;
455 #endif
456 }
457 
458 void appendOSDirSlash(wxString* pString) {
459  wxChar sep = wxFileName::GetPathSeparator();
460  if (pString->Last() != sep) pString->Append(sep);
461 }
462 
463 wxString AbstractPlatform::GetWritableDocumentsDir() {
464  wxString dir;
465 
466 #ifdef __ANDROID__
467  dir = androidGetExtStorageDir(); // Used for Chart storage, typically
468 #else
469  wxStandardPaths& std_path = GetStdPaths();
470  dir = std_path.GetDocumentsDir();
471 #endif
472  return dir;
473 }
474 
475 bool AbstractPlatform::DetectOSDetail(OCPN_OSDetail* detail) {
476  if (!detail) return false;
477 
478  // We take some defaults from build-time definitions
479  detail->osd_name = std::string(PKG_TARGET);
480  detail->osd_version = std::string(PKG_TARGET_VERSION);
481 
482  // Now parse by basic platform
483 #ifdef __linux__
484  if (wxFileExists(_T("/etc/os-release"))) {
485  wxTextFile release_file(_T("/etc/os-release"));
486  if (release_file.Open()) {
487  wxString val;
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);
505  } else {
506  val = str.AfterFirst('=');
507  }
508 
509  if (val.Length()) {
510  detail->osd_names_like = ocpn::split(val.mb_str(), " ");
511  }
512  }
513  }
514 
515  release_file.Close();
516  }
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()) {
522  wxString val;
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());
530  }
531  }
532  upstream_release_file.Close();
533  }
534  }
535  }
536  }
537 #endif
538 
539  // Set the default processor architecture
540  detail->osd_arch = std::string("x86_64");
541 
542  // then see what is actually running.
543  wxPlatformInfo platformInfo = wxPlatformInfo::Get();
544  wxArchitecture arch = platformInfo.GetArchitecture();
545  if (arch == wxARCH_32) detail->osd_arch = std::string("i386");
546 
547 #ifdef ocpnARM
548  // arm supports a multiarch runtime environment
549  // That is, the OS may be 64 bit, but OCPN may be built as a 32 bit binary
550  // So, we cannot trust the wxPlatformInfo architecture determination.
551  detail->osd_arch = std::string("arm64");
552 #ifdef ocpnARMHF
553  detail->osd_arch = std::string("armhf");
554 #endif
555 #endif
556 
557 #ifdef __ANDROID__
558  detail->osd_arch = std::string("arm64");
559  if (arch == wxARCH_32) detail->osd_arch = std::string("armhf");
560 #endif
561 
562 #ifdef __WXOSX__
563  if (IsAppleSilicon() == 1) {
564  if (ProcessIsTranslated() != 1) {
565  detail->osd_arch = std::string("arm64");
566  } else {
567  detail->osd_arch = std::string("x86_64");
568  }
569  } else {
570  detail->osd_arch = std::string("x86_64");
571  }
572 #endif
573 
574  return true;
575 }
576 
577 wxString& AbstractPlatform::GetConfigFileName() {
578  if (m_config_file_name.IsEmpty()) {
579  // Establish the location of the config file
580  wxStandardPaths& std_path = GetStdPaths();
581 
582 #ifdef __WXMSW__
583  m_config_file_name = "opencpn.ini";
584  m_config_file_name.Prepend(GetHomeDir());
585 
586 #elif defined __WXOSX__
587  m_config_file_name =
588  std_path.GetUserConfigDir(); // should be ~/Library/Preferences
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
594  m_config_file_name = GetPrivateDataDir();
595  m_config_file_name.Append("/opencpn.conf");
596  // Usually ~/.var/app/org.opencpn.OpenCPN/config/opencpn.conf
597 #else
598  m_config_file_name = std_path.GetUserDataDir(); // should be ~/.opencpn
599  appendOSDirSlash(&m_config_file_name);
600  m_config_file_name.Append("opencpn.conf");
601 #endif
602 
603  if (g_bportable) {
604  m_config_file_name = GetHomeDir();
605 #ifdef __WXMSW__
606  m_config_file_name += "opencpn.ini";
607 #elif defined __WXOSX__
608  m_config_file_name += "opencpn.ini";
609 #else
610  m_config_file_name += "opencpn.conf";
611 #endif
612  }
613 
614 #ifdef __ANDROID__
615  m_config_file_name = androidGetPrivateDir();
616  appendOSDirSlash(&m_config_file_name);
617  m_config_file_name += "opencpn.conf";
618 #endif
619  if (!g_configdir.empty()) {
620  m_config_file_name = g_configdir;
621  m_config_file_name.Append("/opencpn.conf");
622  }
623  }
624  return m_config_file_name;
625 }
626 
627 bool BasePlatform::InitializeLogFile(void) {
628  // Establish Log File location
629  mlog_file = GetPrivateDataDir();
630  appendOSDirSlash(&mlog_file);
631 
632 #ifdef __WXOSX__
633 
634  wxFileName LibPref(mlog_file); // starts like "~/Library/Preferences/opencpn"
635  LibPref.RemoveLastDir(); // takes off "opencpn"
636  LibPref.RemoveLastDir(); // takes off "Preferences"
637 
638  mlog_file = LibPref.GetFullPath();
639  appendOSDirSlash(&mlog_file);
640 
641  mlog_file.Append(_T("Logs/")); // so, on OS X, opencpn.log ends up in
642  // ~/Library/Logs which makes it accessible to
643  // Applications/Utilities/Console....
644 #endif
645 
646  // create the opencpn "home" directory if we need to
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"));
651  return false;
652  }
653 
654  // create the opencpn "log" directory if we need to
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"));
659  return false;
660  }
661  }
662 
663  mlog_file.Append(_T("opencpn.log"));
664  wxString logit = mlog_file;
665 
666 #ifdef __ANDROID__
667  wxCharBuffer abuf = mlog_file.ToUTF8();
668  qDebug() << "logfile " << abuf.data();
669 #endif
670 
671  // Constrain the size of the log file
672  if (::wxFileExists(mlog_file)) {
673  if (wxFileName::GetSize(mlog_file) > 1000000) {
674  wxString oldlog = mlog_file;
675  oldlog.Append(_T(".log"));
676  // Defer the showing of this messagebox until the system locale is
677  // established.
678  large_log_message = (_T("Old log will be moved to opencpn.log.log"));
679  ::wxRenameFile(mlog_file, oldlog);
680  }
681  }
682 #ifdef __ANDROID__
683  if (::wxFileExists(mlog_file)) {
684  // Force new logfile for each instance
685  // TODO Remove this behaviour on Release
686  ::wxRemoveFile(mlog_file);
687  }
688 #endif
689 
690  if (wxLog::GetLogLevel() > wxLOG_User) wxLog::SetLogLevel(wxLOG_Info);
691 
692  auto logger = new OcpnLog(mlog_file.mb_str());
693  if (m_old_logger) {
694  delete m_old_logger;
695  }
696  m_old_logger = wxLog::SetActiveTarget(logger);
697 
698  return true;
699 }
700 
701 void AbstractPlatform::CloseLogFile(void) {
702  if (m_old_logger) {
703  delete wxLog::SetActiveTarget(m_old_logger);
704  m_old_logger = nullptr;
705  }
706 }
707 
709  if (g_bportable) {
710  wxString sep = wxFileName::GetPathSeparator();
711  wxString ret = GetPrivateDataDir() + sep + _T("plugins");
712  return ret;
713  }
714 
715  if (m_pluginDataPath != "") {
716  return m_pluginDataPath;
717  }
718  wxString dirs("");
719 #ifdef __ANDROID__
720  wxString pluginDir = GetPrivateDataDir() + "/plugins";
721  dirs += pluginDir;
722 #else
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) {
729  dirs = GetWinPluginBaseDir();
730  } else if (osSystemId & wxOS_MAC) {
731  dirs = "/Applications/OpenCPN.app/Contents/SharedSupport/plugins;";
732  dirs +=
733  "~/Library/Application Support/OpenCPN/Contents/SharedSupport/plugins";
734  }
735 #endif
736 
737  m_pluginDataPath = ExpandPaths(dirs, this);
738  if (m_pluginDataPath != "") {
739  m_pluginDataPath += ";";
740  }
741  m_pluginDataPath += GetPluginDir();
742  if (m_pluginDataPath.EndsWith(wxFileName::GetPathSeparator())) {
743  m_pluginDataPath.RemoveLast();
744  }
745  wxLogMessage("Using plugin data path: %s", m_pluginDataPath.mb_str().data());
746  return m_pluginDataPath;
747 }
748 
749 #ifdef __ANDROID__
750 void AbstractPlatform::ShowBusySpinner() {
751  if (!m_isBusy) {
752  androidShowBusyIcon();
753  m_isBusy = true;
754  }
755 }
756 #else
757 void AbstractPlatform::ShowBusySpinner() {
758  if (!m_isBusy) {
759  ::wxBeginBusyCursor();
760  m_isBusy = true;
761  }
762 }
763 #endif
764 
765 #ifdef __ANDROID__
766 void AbstractPlatform::HideBusySpinner() {
767  if (m_isBusy) {
768  androidHideBusyIcon();
769  m_isBusy = false;
770  }
771 }
772 #else
773 void AbstractPlatform::HideBusySpinner() {
774  if (m_isBusy) {
775  ::wxEndBusyCursor();
776  m_isBusy = false;
777  }
778 }
779 #endif
780 
781 // getDisplaySize
782 
783 
784 
785 #if defined(__ANDROID__)
786 wxSize BasePlatform::getDisplaySize() { return getAndroidDisplayDimensions(); }
787 
788 #else
789 wxSize BasePlatform::getDisplaySize() {
790  return wxSize(0, 0);
791 }
792 #endif
793 
794 // GetDisplaySizeMM
795 double BasePlatform::GetDisplaySizeMM() {
796  if(m_displaySizeMMOverride.size() > 0 && m_displaySizeMMOverride[0] > 0) {
797  return m_displaySizeMMOverride[0];
798  }
799  double ret = 0;
800 
801 #ifdef __ANDROID__
802  ret = GetAndroidDisplaySize();
803 #endif
804 
805  wxLogDebug("Detected display size (horizontal): %d mm", (int)ret);
806  return ret;
807 }
808 
809 #if defined(__ANDROID__)
810 double BasePlatform::GetDisplayDPmm() { return getAndroidDPmm(); }
811 
812 #else
813 double BasePlatform::GetDisplayDPmm() {
814  if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
815  double r = getDisplaySize().x; // dots
816  return r / GetDisplaySizeMM();
817  } else {
818  // This is a console app... assuming 300 DPI ~ 12 DPmm
819  return 12.0;
820  }
821 }
822 #endif
823 
824 
825 double AbstractPlatform::GetDisplayDIPMult(wxWindow *win) {
826  double rv = 1.0;
827 #ifdef __WXMSW__
828  if (win)
829  rv = (double)(win->ToDIP(100))/100.;
830 #endif
831  return rv;
832 }
833 
834 unsigned int AbstractPlatform::GetSelectRadiusPix() {
835  return GetDisplayDPmm() *
836  (g_btouch ? g_selection_radius_touch_mm : g_selection_radius_mm);
837 }
838 
839 #ifdef __WXMSW__
840 
841 #define NAME_SIZE 128
842 
843 const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08,
844  0x00, 0x2b, 0xe1, 0x03, 0x18};
845 
846 // Assumes hDevRegKey is valid
847 bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, int *WidthMm,
848  int *HeightMm) {
849  DWORD dwType, AcutalValueNameLength = NAME_SIZE;
850  TCHAR valueName[NAME_SIZE];
851 
852  BYTE EDIDdata[1024];
853  DWORD edidsize = sizeof(EDIDdata);
854 
855  for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS;
856  ++i) {
857  retValue = RegEnumValue(hDevRegKey, i, &valueName[0],
858  &AcutalValueNameLength, NULL, &dwType,
859  EDIDdata, // buffer
860  &edidsize); // buffer size
861 
862  if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName, _T("EDID")))
863  continue;
864 
865  *WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
866  *HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
867 
868  return true; // valid EDID found
869  }
870 
871  return false; // EDID not found
872 }
873 
874 bool GetSizeForDevID(wxString &TargetDevID, int *WidthMm, int *HeightMm) {
875  HDEVINFO devInfo =
876  SetupDiGetClassDevsEx(&GUID_CLASS_MONITOR, // class GUID
877  NULL, // enumerator
878  NULL, // HWND
879  DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
880  NULL, // device info, create a new one.
881  NULL, // machine name, local machine
882  NULL); // reserved
883 
884  if (NULL == devInfo) return false;
885 
886  bool bRes = false;
887 
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);
892 
893  if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
894  wchar_t Instance[80];
895  SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH,
896  NULL);
897  wxString instance(Instance);
898  if (instance.Upper().Find(TargetDevID.Upper()) == wxNOT_FOUND) continue;
899 
900  HKEY hDevRegKey = SetupDiOpenDevRegKey(
901  devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
902 
903  if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) continue;
904 
905  bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
906 
907  RegCloseKey(hDevRegKey);
908  }
909  }
910  SetupDiDestroyDeviceInfoList(devInfo);
911  return bRes;
912 }
913 
914 bool AbstractPlatform::GetWindowsMonitorSize(int *width, int *height) {
915  bool bFoundDevice = true;
916 
917  if (m_monitorWidth < 10) {
918  int WidthMm = 0;
919  int HeightMm = 0;
920 
921  DISPLAY_DEVICE dd;
922  dd.cb = sizeof(dd);
923  DWORD dev = 0; // device index
924  int id = 1; // monitor number, as used by Display Properties > Settings
925 
926  wxString DeviceID;
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);
932  DWORD devMon = 0;
933 
934  while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) &&
935  !bFoundDevice) {
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('\\'));
941 
942  bFoundDevice = GetSizeForDevID(DeviceID, &WidthMm, &HeightMm);
943  }
944  devMon++;
945 
946  ZeroMemory(&ddMon, sizeof(ddMon));
947  ddMon.cb = sizeof(ddMon);
948  }
949 
950  ZeroMemory(&dd, sizeof(dd));
951  dd.cb = sizeof(dd);
952  dev++;
953  }
954  m_monitorWidth = WidthMm;
955  m_monitorHeight = HeightMm;
956  }
957 
958  if (width) *width = m_monitorWidth;
959  if (height) *height = m_monitorHeight;
960 
961  return bFoundDevice;
962 }
963 
964 #endif
wxString * GetPrivateDataDirPtr()
Legacy compatibility syntactic sugar for GetPrivateDataDir().
wxString & GetPluginDir()
The original in-tree plugin directory, sometimes not user-writable.
wxString GetWinPluginBaseDir()
Base directory for user writable windows plugins, reflects winPluginDir option, defaults to LOCALAPPD...
wxString & DefaultPrivateDataDir()
Return dir path for opencpn.log, etc., does not respect -c option.
wxString GetPluginDataPath()
Return ';'-separated list of base directories for plugin data.
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
Logging interface.
Definition: logger.h:62
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.