KiCad PCB EDA Suite
Loading...
Searching...
No Matches
windows/environment.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 Ian McInerney <Ian.S.McInerney at ieee.org>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22#include <wx/intl.h>
23#include <wx/filename.h>
24#include <wx/stdpaths.h>
25#include <wx/string.h>
26#include <wx/tokenzr.h>
27#include <wx/app.h>
28#include <wx/uri.h>
29#include <wx/window.h>
30
31#include <filesystem>
32#include <string>
33#include <windows.h>
34#include <shellapi.h>
35#include <shlwapi.h>
36#include <propkey.h>
37#include <propvarutil.h>
38#if defined( __MINGW32__ )
39 #include <shobjidl.h>
40#else
41 #include <shobjidl_core.h>
42#endif
43#include <winhttp.h>
44
45#include <softpub.h>
46
47#if defined( __MINGW32__ )
48 #include <shlobj.h>
49#else
50 #include <shlobj_core.h>
51#endif
52
53#include <wincrypt.h>
54#include <wintrust.h>
55
56#define INCLUDE_KICAD_VERSION // fight me
57#include <kicad_build_version.h>
58
59
61{
62 ::SetCurrentProcessExplicitAppUserModelID( GetAppUserModelId().wc_str() );
63}
64
65
66bool KIPLATFORM::ENV::MoveToTrash( const wxString& aPath, wxString& aError )
67{
68 // The filename field must be a double-null terminated string
69 wxString temp = aPath + '\0';
70
71 SHFILEOPSTRUCT fileOp;
72 ::ZeroMemory( &fileOp, sizeof( fileOp ) );
73
74 fileOp.hwnd = nullptr; // Set to null since there is no progress dialog
75 fileOp.wFunc = FO_DELETE;
76 fileOp.pFrom = temp.c_str();
77 fileOp.pTo = nullptr; // Set to to NULL since we aren't moving the file
78 fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_NOCONFIRMATION | FOF_SILENT;
79
80 int eVal = SHFileOperation( &fileOp );
81
82 if( eVal != 0 )
83 {
84 aError = wxString::Format( _( "Error code: %d" ), eVal );
85 return false;
86 }
87
88 return true;
89}
90
91
92bool KIPLATFORM::ENV::IsNetworkPath( const wxString& aPath )
93{
94 return ::PathIsNetworkPathW( aPath.wc_str() );
95}
96
97
98bool KIPLATFORM::ENV::IsRemovablePath( const wxString& aPath )
99{
100 // A link on a USB drive may actually refer to a network share or an internal drive.
101 std::error_code error;
102 std::filesystem::path path = std::filesystem::canonical( aPath.ToStdWstring(), error );
103
104 if( error || path.empty() )
105 return false;
106
107 // The volume root is a prefix of the resolved path, plus a possible trailing separator.
108 std::wstring root( path.native().size() + 2, L'\0' );
109
110 return GetVolumePathNameW( path.c_str(), root.data(), static_cast<DWORD>( root.size() ) )
111 && GetDriveTypeW( root.c_str() ) == DRIVE_REMOVABLE;
112}
113
114
116{
117 // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
118 // complains about not existing app. so use a dummy app
119 if( wxTheApp == nullptr )
120 {
121 wxApp dummy;
122 return wxStandardPaths::Get().GetDocumentsDir();
123 }
124
125 return wxStandardPaths::Get().GetDocumentsDir();
126}
127
128
130{
131 // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
132 // complains about not existing app. so use a dummy app
133 if( wxTheApp == nullptr )
134 {
135 wxApp dummy;
136 return wxStandardPaths::Get().GetUserConfigDir();
137 }
138
139 return wxStandardPaths::Get().GetUserConfigDir();
140}
141
142
144{
145 // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
146 // complains about not existing app. so use a dummy app
147 if( wxTheApp == nullptr )
148 {
149 wxApp dummy;
150 return wxStandardPaths::Get().GetUserDataDir();
151 }
152
153 return wxStandardPaths::Get().GetUserDataDir();
154}
155
156
158{
159 // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
160 // complains about not existing app. so use a dummy app
161 if( wxTheApp == nullptr )
162 {
163 wxApp dummy;
164 return wxStandardPaths::Get().GetUserLocalDataDir();
165 }
166
167 return wxStandardPaths::Get().GetUserLocalDataDir();
168}
169
170
172{
173 // Unfortunately AppData/Local is the closest analog to "Cache" directories of other platforms
174
175 // Make sure we don't include the "appinfo" (appended app name)
176
177 // If called by a python script in stand-alone (outside KiCad), wxStandardPaths::Get()
178 // complains about not existing app. so use a dummy app
179 if( wxTheApp == nullptr )
180 {
181 wxApp dummy;
182 wxStandardPaths::Get().UseAppInfo( wxStandardPaths::AppInfo_None );
183
184 return wxStandardPaths::Get().GetUserLocalDataDir();
185 }
186
187 wxStandardPaths::Get().UseAppInfo( wxStandardPaths::AppInfo_None );
188
189 return wxStandardPaths::Get().GetUserLocalDataDir();
190}
191
192
193bool KIPLATFORM::ENV::GetSystemProxyConfig( const wxString& aURL, PROXY_CONFIG& aCfg )
194{
195 // Original source from Microsoft sample (public domain)
196 // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/WinhttpProxy/cpp/GetProxy.cpp#L844
197 bool autoProxyDetect = false;
198 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig = { 0 };
199 WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions = { 0 };
200 WINHTTP_PROXY_INFO autoProxyInfo = { 0 };
201 HINTERNET proxyResolveSession = NULL;
202 bool success = false;
203
204 wxURI uri( aURL );
205
206 LPWSTR proxyStr = NULL;
207 LPWSTR bypassProxyStr = NULL;
208
209 if( WinHttpGetIEProxyConfigForCurrentUser( &ieProxyConfig ) )
210 {
211 // welcome to the wonderful world of IE
212 // we use the ie config simply to handle it off to the other win32 api
213 if( ieProxyConfig.fAutoDetect )
214 {
215 autoProxyDetect = true;
216 }
217
218 if( ieProxyConfig.lpszAutoConfigUrl != NULL )
219 {
220 autoProxyDetect = true;
221 autoProxyOptions.lpszAutoConfigUrl = ieProxyConfig.lpszAutoConfigUrl;
222 }
223 }
224 else if( GetLastError() == ERROR_FILE_NOT_FOUND )
225 {
226 // this is the only error code where we want to continue attempting to find a proxy
227 autoProxyDetect = true;
228 }
229
230 if( autoProxyDetect )
231 {
232 proxyResolveSession =
233 WinHttpOpen( NULL, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME,
234 WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC );
235
236 if( proxyResolveSession )
237 {
238 // either we use the ie url or we set the auto detect mode
239 if( autoProxyOptions.lpszAutoConfigUrl != NULL )
240 {
241 autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
242 }
243 else
244 {
245 autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
246 autoProxyOptions.dwAutoDetectFlags =
247 WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
248 }
249
250 // dont do auto logon at first, this allows windows to use an cache
251 // per https://docs.microsoft.com/en-us/windows/win32/winhttp/autoproxy-cache
252 autoProxyOptions.fAutoLogonIfChallenged = FALSE;
253
254 autoProxyDetect = WinHttpGetProxyForUrl( proxyResolveSession, aURL.c_str(),
255 &autoProxyOptions, &autoProxyInfo );
256
257 if( !autoProxyDetect && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE )
258 {
259 autoProxyOptions.fAutoLogonIfChallenged = TRUE;
260
261 // try again with auto login now
262 autoProxyDetect = WinHttpGetProxyForUrl( proxyResolveSession, aURL.c_str(),
263 &autoProxyOptions, &autoProxyInfo );
264 }
265
266 if( autoProxyDetect )
267 {
268 if( autoProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY )
269 {
270 proxyStr = autoProxyInfo.lpszProxy;
271 bypassProxyStr = autoProxyInfo.lpszProxyBypass;
272 }
273 }
274
275 WinHttpCloseHandle( proxyResolveSession );
276 }
277 }
278
279 if( !autoProxyDetect && ieProxyConfig.lpszProxy != NULL )
280 {
281 proxyStr = ieProxyConfig.lpszProxy;
282 bypassProxyStr = ieProxyConfig.lpszProxyBypass;
283 }
284
285 bool bypassed = false;
286
287 if( bypassProxyStr != NULL )
288 {
289 wxStringTokenizer tokenizer( bypassProxyStr, ";" );
290
291 while( tokenizer.HasMoreTokens() )
292 {
293 wxString host = tokenizer.GetNextToken();
294
295 if( host == uri.GetServer() )
296 {
297 // the given url has a host in the proxy bypass list
298 return false;
299 }
300
301 // <local> is a special case that says all local sites bypass
302 // the windows way for considering local is any host without periods in the name that would imply
303 // some non-internal dns resolution
304 if( host == "<local>" )
305 {
306 if( !uri.GetServer().Contains( "." ) )
307 {
308 // great its a local uri that is bypassed
309 bypassed = true;
310 break;
311 }
312 }
313 }
314 }
315
316 if( !bypassed && proxyStr != NULL )
317 {
318 // proxyStr can be in the following format per MSDN
319 //([<scheme>=][<scheme>"://"]<server>[":"<port>])
320 //and separated by semicolons or whitespace
321 wxStringTokenizer tokenizer( proxyStr, "; \t" );
322
323 while( tokenizer.HasMoreTokens() )
324 {
325 wxString entry = tokenizer.GetNextToken();
326
327 // deal with the [<scheme>=] part, which may or may not exist
328 if( entry.Contains( "=" ) )
329 {
330 wxString scheme = entry.BeforeFirst( '=' ).Lower();
331 entry = entry.AfterFirst( '=' );
332
333 // skip processing if the scheme doesnt match
334 if( scheme != uri.GetScheme().Lower() )
335 {
336 continue;
337 }
338
339 // we continue with the [<scheme>=] stripped off if we matched
340 }
341
342 // is the entry left not empty? we just take the first result
343 // : and :: are also special cases we want to ignore
344 if( entry != "" && entry != ":" && entry != "::" )
345 {
346 aCfg.host = entry;
347 success = true;
348 break;
349 }
350 }
351 }
352
353
354 // We have to clean up the strings the win32 api returned
355 if( autoProxyInfo.lpszProxy )
356 {
357 GlobalFree( autoProxyInfo.lpszProxy );
358 autoProxyInfo.lpszProxy = NULL;
359 }
360
361 if( autoProxyInfo.lpszProxyBypass )
362 {
363 GlobalFree( autoProxyInfo.lpszProxyBypass );
364 autoProxyInfo.lpszProxyBypass = NULL;
365 }
366
367 if( ieProxyConfig.lpszAutoConfigUrl != NULL )
368 {
369 GlobalFree( ieProxyConfig.lpszAutoConfigUrl );
370 ieProxyConfig.lpszAutoConfigUrl = NULL;
371 }
372
373 if( ieProxyConfig.lpszProxy != NULL )
374 {
375 GlobalFree( ieProxyConfig.lpszProxy );
376 ieProxyConfig.lpszProxy = NULL;
377 }
378
379 if( ieProxyConfig.lpszProxyBypass != NULL )
380 {
381 GlobalFree( ieProxyConfig.lpszProxyBypass );
382 ieProxyConfig.lpszProxyBypass = NULL;
383 }
384
385 return success;
386}
387
388
389bool KIPLATFORM::ENV::VerifyFileSignature( const wxString& aPath )
390{
391 WINTRUST_FILE_INFO fileData;
392 memset( &fileData, 0, sizeof( fileData ) );
393 fileData.cbStruct = sizeof( WINTRUST_FILE_INFO );
394 fileData.pcwszFilePath = aPath.wc_str();
395
396 // verifies entire certificate chain
397 GUID policy = WINTRUST_ACTION_GENERIC_VERIFY_V2;
398
399 WINTRUST_DATA trustData;
400 memset( &trustData, 0, sizeof( trustData ) );
401
402 trustData.cbStruct = sizeof( trustData );
403 trustData.dwUIChoice = WTD_UI_NONE;
404 // revocation checking incurs latency penalities due to need for online queries
405 trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
406 trustData.dwUnionChoice = WTD_CHOICE_FILE;
407 trustData.dwStateAction = WTD_STATEACTION_VERIFY;
408 trustData.pFile = &fileData;
409
410
411 bool verified = false;
412 LONG status = WinVerifyTrust( NULL, &policy, &trustData );
413
414 verified = ( status == ERROR_SUCCESS );
415
416 // Cleanup/release (yes its weird looking)
417 trustData.dwStateAction = WTD_STATEACTION_CLOSE;
418 WinVerifyTrust( NULL, &policy, &trustData );
419
420 return verified;
421}
422
423
425{
426 // The application model id allows for taskbar grouping
427 // However, be warned, this cannot be too unique like per-process
428 // Because longer scope Windows features, such as "Pin to Taskbar"
429 // on a running application, depend on this being consistent.
430 std::vector<wxString> modelIdComponents;
431 modelIdComponents.push_back( wxS( "Kicad" ) );
432 modelIdComponents.push_back( wxS( "Kicad" ) );
433 modelIdComponents.push_back( wxTheApp->GetAppName() );
434 modelIdComponents.push_back( KICAD_MAJOR_MINOR_VERSION );
435
436 wxString modelId;
437 for( const auto& str : modelIdComponents )
438 {
439 modelId += str;
440 modelId += wxS( "." );
441 }
442
443 modelId.RemoveLast(); // remove trailing dot
444 modelId.Replace( wxS( " " ), wxS( "_" ) ); // remove spaces sanity
445
446 // the other limitation is 127 characters but we arent trying to hit that limit yet
447
448 return modelId;
449}
450
451
452void KIPLATFORM::ENV::SetAppDetailsForWindow( wxWindow* aWindow, const wxString& aRelaunchCommand,
453 const wxString& aRelaunchDisplayName )
454{
455 IPropertyStore* pps;
456 HRESULT hr = ::SHGetPropertyStoreForWindow( aWindow->GetHWND(), IID_PPV_ARGS( &pps ) );
457 if( SUCCEEDED( hr ) )
458 {
459 PROPVARIANT pv;
460
461 // This is required for any the other properties to actually work
462 hr = ::InitPropVariantFromString( GetAppUserModelId().wc_str(), &pv );
463
464 if( SUCCEEDED( hr ) )
465 {
466 hr = pps->SetValue( PKEY_AppUserModel_ID, pv );
467 PropVariantClear( &pv );
468 }
469
470
471 if( !aRelaunchCommand.empty() )
472 {
473 hr = ::InitPropVariantFromString( aRelaunchCommand.wc_str(), &pv );
474 }
475 else
476 {
477 // empty var
478 ::PropVariantInit( &pv );
479 }
480
481 if( SUCCEEDED( hr ) )
482 {
483 hr = pps->SetValue( PKEY_AppUserModel_RelaunchCommand, pv );
484 PropVariantClear( &pv );
485 }
486
487 if( !aRelaunchDisplayName.empty() )
488 {
489 hr = ::InitPropVariantFromString( aRelaunchDisplayName.wc_str(), &pv );
490 }
491 else
492 {
493 // empty var
494 ::PropVariantInit( &pv );
495 }
496
497 if( SUCCEEDED( hr ) )
498 {
499 hr = pps->SetValue( PKEY_AppUserModel_RelaunchDisplayNameResource, pv );
500 PropVariantClear( &pv );
501 }
502
503 pps->Release();
504 }
505}
506
507
509{
510 return ::GetCommandLine();
511}
512
513
514void KIPLATFORM::ENV::AddToRecentDocs( const wxString& aPath )
515{
516 IShellItem* psi = nullptr;
517 HRESULT hr = SHCreateItemFromParsingName( aPath.wc_str(), NULL, IID_PPV_ARGS( &psi ) );
518
519 if( SUCCEEDED( hr ) )
520 {
521 wxString appID = GetAppUserModelId();
522 SHARDAPPIDINFO info;
523 info.psi = psi;
524 info.pszAppID = appID.wc_str();
525 ::SHAddToRecentDocs( SHARD_APPIDINFO, &info );
526
527 psi->Release();
528 }
529
530 ::SHAddToRecentDocs( SHARD_PATHW, aPath.wc_str() );
531}
#define _(s)
bool IsNetworkPath(const wxString &aPath)
Determines if a given path is a network shared file apth On Windows for example, any form of path is ...
void Init()
Perform environment initialization tasks.
bool IsRemovablePath(const wxString &aPath)
wxString GetCommandLineStr()
bool GetSystemProxyConfig(const wxString &aURL, PROXY_CONFIG &aCfg)
Retrieves platform level proxying requirements to reach the given url.
wxString GetUserDataPath()
Retrieves the operating system specific path for a user's data store.
wxString GetDocumentsPath()
Retrieves the operating system specific path for a user's documents.
wxString GetAppUserModelId()
Retrieves the app user model id, a special string used for taskbar grouping on Windows 7 and later.
void SetAppDetailsForWindow(wxWindow *aWindow, const wxString &aRelaunchCommand, const wxString &aRelaunchDisplayName)
Sets the relaunch command for taskbar pins, this is intended for Windows.
void AddToRecentDocs(const wxString &aPath)
bool MoveToTrash(const wxString &aPath, wxString &aError)
Move the specified file/directory to the trash bin/recycle bin.
wxString GetUserLocalDataPath()
Retrieves the operating system specific path for a user's local data store.
wxString GetUserConfigPath()
Retrieves the operating system specific path for a user's configuration store.
wxString GetUserCachePath()
Retrieves the operating system specific path for user's application cache.
bool VerifyFileSignature(const wxString &aPath)
Validates the code signing signature of a given file This is most likely only ever going to be applic...
std::vector< FAB_LAYER_COLOR > dummy
std::string path