KiCad PCB EDA Suite
Loading...
Searching...
No Matches
model_preview_manager_windows.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-3.0.html
19 */
20
23
24#include <wx/filename.h>
25#include <wx/stdpaths.h>
26#include <wx/utils.h>
27
28#include <windows.h>
29#include <winver.h>
30
31#include <array>
32#include <vector>
33
34#pragma comment( lib, "version.lib" )
35
36namespace
37{
38constexpr wchar_t HANDLER_CLSID[] = L"{072BD71D-CB70-4CC8-8F08-D0A763A17C2E}";
39constexpr wchar_t HANDLER_APPID[] = L"{6D2B5079-2F0B-48DD-AB7F-97CEC514D30B}";
40constexpr wchar_t PREVIEW_HANDLER_IID[] = L"{8895B1C6-B41F-4C1C-A562-0D564250836F}";
41constexpr wchar_t REQUIREMENT_ID[] = L"windows-preview-handler-v1";
42constexpr wchar_t HANDLER_NAME[] = L"kicad_3d_model_previewer.dll";
43// Must match fileTypes[] in the preview handler's dllmain.cpp, which writes the registration.
44constexpr std::array<const wchar_t*, 9> EXTENSIONS = { L".stp", L".stpz", L".step", L".stepz", L".igs",
45 L".iges", L".wrl", L".wrz", L".x3d" };
46
47enum class READ_RESULT
48{
49 FOUND,
50 MISSING,
51 READ_ERROR
52};
53
54READ_RESULT ReadRegistryString( HKEY aRoot, const wxString& aKey, const wchar_t* aValueName, wxString& aValue )
55{
56 std::array<wchar_t, 2048> value;
57 DWORD size = static_cast<DWORD>( value.size() * sizeof( wchar_t ) );
58 LONG result = RegGetValueW( aRoot, aKey.wc_str(), aValueName,
59 RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, nullptr,
60 value.data(), &size );
61
62 if( result == ERROR_FILE_NOT_FOUND )
63 return READ_RESULT::MISSING;
64
65 if( result != ERROR_SUCCESS )
66 return READ_RESULT::READ_ERROR;
67
68 aValue = value.data();
69
70 return READ_RESULT::FOUND;
71}
72
73
74READ_RESULT ReadString( const wxString& aKey, const wchar_t* aValueName, wxString& aValue )
75{
76 return ReadRegistryString( HKEY_CURRENT_USER, aKey, aValueName, aValue );
77}
78
79
80READ_RESULT ReadClassString( const wxString& aKey, const wchar_t* aValueName, wxString& aValue )
81{
82 return ReadRegistryString( HKEY_CLASSES_ROOT, aKey, aValueName, aValue );
83}
84
85wxString BundledHandlerPath()
86{
87 if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
88 {
89 wxFileName fn( wxStandardPaths::Get().GetExecutablePath() );
90 fn.AppendDir( wxS( ".." ) );
91 fn.AppendDir( wxS( "utils" ) );
92 fn.AppendDir( wxS( "preview_handlers" ) );
93 fn.AppendDir( wxS( "kicad_3d_model_preview_handler" ) );
94 fn.SetFullName( HANDLER_NAME );
95
96 return fn.GetFullPath();
97 }
98 else
99 {
100 wxFileName fn( wxStandardPaths::Get().GetExecutablePath() );
101 fn.SetFullName( HANDLER_NAME );
102
103 return fn.GetFullPath();
104 }
105}
106
107wxString ExpandRegistryPath( const wxString& aPath )
108{
109 DWORD required = ExpandEnvironmentStringsW( aPath.wc_str(), nullptr, 0 );
110
111 if( required == 0 )
112 return aPath;
113
114 std::vector<wchar_t> expanded( required );
115
116 if( ExpandEnvironmentStringsW( aPath.wc_str(), expanded.data(), required ) != required )
117 return aPath;
118
119 return expanded.data();
120}
121
122wxString FileVersion( const wxString& aPath )
123{
124 DWORD ignored = 0;
125 DWORD size = GetFileVersionInfoSizeW( aPath.wc_str(), &ignored );
126
127 if( !size )
128 return {};
129
130 std::vector<BYTE> data( size );
131 VS_FIXEDFILEINFO* info = nullptr;
132 UINT infoSize = 0;
133
134 if( !GetFileVersionInfoW( aPath.wc_str(), 0, size, data.data() )
135 || !VerQueryValueW( data.data(), L"\\", reinterpret_cast<void**>( &info ), &infoSize ) || !info )
136 return {};
137
138 return wxString::Format( wxS( "%u.%u.%u.%u" ), HIWORD( info->dwFileVersionMS ), LOWORD( info->dwFileVersionMS ),
139 HIWORD( info->dwFileVersionLS ), LOWORD( info->dwFileVersionLS ) );
140}
141} // namespace
142
143
145{
147 snapshot.requirementId = REQUIREMENT_ID;
148 wxString bundledPath = BundledHandlerPath();
149
150 if( bundledPath.empty() || !wxFileName::FileExists( bundledPath ) )
151 {
152 return ClassifyWindowsModelPreviewStatus( snapshot );
153 }
154
155 snapshot.bundledPresent = true;
156 snapshot.bundledVersion = FileVersion( bundledPath );
157 snapshot.requirementId = wxString::Format( wxS( "%ls-%s" ), REQUIREMENT_ID, snapshot.bundledVersion );
158
159 bool missingAssociation = false;
160 bool foreignOwner = false;
161
162 for( const wchar_t* extension : EXTENSIONS )
163 {
164 wxString owner;
165 wxString mergedKey = wxString::Format( L"%ls\\shellex\\%ls", extension, PREVIEW_HANDLER_IID );
166
167 switch( ReadClassString( mergedKey, nullptr, owner ) )
168 {
169 case READ_RESULT::MISSING:
170 missingAssociation = true;
171 break;
172
173 case READ_RESULT::READ_ERROR:
175 return ClassifyWindowsModelPreviewStatus( snapshot );
176
177 case READ_RESULT::FOUND:
178 foreignOwner = foreignOwner || owner.CmpNoCase( HANDLER_CLSID ) != 0;
179 break;
180 }
181 }
182
183 if( foreignOwner )
184 {
185 snapshot.foreignOwner = true;
186 return ClassifyWindowsModelPreviewStatus( snapshot );
187 }
188
189 wxString registeredPath;
190 wxString inprocKey = wxString::Format( L"Software\\Classes\\CLSID\\%ls\\InprocServer32", HANDLER_CLSID );
191 wxString mergedInprocKey = wxString::Format( L"CLSID\\%ls\\InprocServer32", HANDLER_CLSID );
192 READ_RESULT pathResult = ReadClassString( mergedInprocKey, nullptr, registeredPath );
193
194 if( pathResult == READ_RESULT::READ_ERROR )
195 {
197 return ClassifyWindowsModelPreviewStatus( snapshot );
198 }
199
200 if( pathResult == READ_RESULT::MISSING )
201 {
202 snapshot.module = MODEL_PREVIEW_OBSERVATION::MISSING;
203 return ClassifyWindowsModelPreviewStatus( snapshot );
204 }
205
206 registeredPath = ExpandRegistryPath( registeredPath );
207 wxFileName registeredFile( registeredPath );
208 wxFileName bundledFile( bundledPath );
209 registeredFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_DOTS | wxPATH_NORM_CASE | wxPATH_NORM_LONG );
210 bundledFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_DOTS | wxPATH_NORM_CASE | wxPATH_NORM_LONG );
211
212 if( registeredFile.GetFullPath().CmpNoCase( bundledFile.GetFullPath() ) != 0 )
213 {
214 snapshot.module = MODEL_PREVIEW_OBSERVATION::PRESENT;
215 snapshot.foreignOwner = true;
216 return ClassifyWindowsModelPreviewStatus( snapshot );
217 }
218
219 snapshot.module = MODEL_PREVIEW_OBSERVATION::PRESENT;
220 snapshot.sameModule = true;
221
222 wxString registeredVersion;
223 READ_RESULT registeredVersionResult = ReadString( inprocKey, L"KiCadHandlerVersion", registeredVersion );
224
225 if( registeredVersionResult == READ_RESULT::READ_ERROR )
226 {
228 return ClassifyWindowsModelPreviewStatus( snapshot );
229 }
230
231 snapshot.registeredVersion = registeredVersion;
232
233 for( const wchar_t* extension : EXTENSIONS )
234 {
235 wxString owner;
236 wxString key = wxString::Format( L"Software\\Classes\\%ls\\shellex\\%ls", extension, PREVIEW_HANDLER_IID );
237 READ_RESULT ownerResult = ReadString( key, nullptr, owner );
238
239 if( ownerResult == READ_RESULT::READ_ERROR )
240 {
242 return ClassifyWindowsModelPreviewStatus( snapshot );
243 }
244
245 if( ownerResult == READ_RESULT::FOUND && owner.CmpNoCase( HANDLER_CLSID ) != 0 )
246 {
247 snapshot.foreignOwner = true;
248 return ClassifyWindowsModelPreviewStatus( snapshot );
249 }
250 }
251
252 wxString surrogate;
253 wxString appIdKey = wxString::Format( L"Software\\Classes\\AppID\\%ls", HANDLER_APPID );
254 READ_RESULT appIdResult = ReadString( appIdKey, L"DllSurrogate", surrogate );
255
256 if( appIdResult == READ_RESULT::READ_ERROR )
257 {
259 return ClassifyWindowsModelPreviewStatus( snapshot );
260 }
261
262 wxString clsidAppId;
263 wxString threadingModel;
264 wxString clsidKey = wxString::Format( L"Software\\Classes\\CLSID\\%ls", HANDLER_CLSID );
265 READ_RESULT clsidAppIdResult = ReadString( clsidKey, L"AppID", clsidAppId );
266 READ_RESULT threadingResult = ReadString( inprocKey, L"ThreadingModel", threadingModel );
267
268 if( clsidAppIdResult == READ_RESULT::READ_ERROR || threadingResult == READ_RESULT::READ_ERROR )
269 {
271 return ClassifyWindowsModelPreviewStatus( snapshot );
272 }
273
274 wxString previewHandlerName;
275 READ_RESULT previewListResult = ReadString( wxS( "Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers" ),
276 HANDLER_CLSID, previewHandlerName );
277
278 if( previewListResult == READ_RESULT::READ_ERROR )
279 {
281 return ClassifyWindowsModelPreviewStatus( snapshot );
282 }
283
284 snapshot.associations =
286 snapshot.surrogate = appIdResult == READ_RESULT::FOUND && !surrogate.empty() ? MODEL_PREVIEW_OBSERVATION::PRESENT
288 snapshot.classMetadata = clsidAppIdResult == READ_RESULT::FOUND && threadingResult == READ_RESULT::FOUND
289 && clsidAppId.CmpNoCase( HANDLER_APPID ) == 0
290 && threadingModel.CmpNoCase( wxS( "Apartment" ) ) == 0
293 snapshot.previewList = previewListResult == READ_RESULT::FOUND && !previewHandlerName.empty()
296 return ClassifyWindowsModelPreviewStatus( snapshot );
297}
298
299
301{
303
304 if( !status.canRepairRegistration )
305 {
306 aError = status.detail;
307 return false;
308 }
309
310 wxString handlerPath = BundledHandlerPath();
311 HMODULE module = LoadLibraryExW( handlerPath.wc_str(), nullptr,
312 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS );
313
314 if( !module )
315 {
316 aError = wxString::Format( wxS( "Unable to load the Windows preview handler (error %lu)." ), GetLastError() );
317 return false;
318 }
319
320 using REGISTER_SERVER = HRESULT( STDAPICALLTYPE* )();
321 REGISTER_SERVER registerServer = reinterpret_cast<REGISTER_SERVER>( GetProcAddress( module, "DllRegisterServer" ) );
322 HRESULT result = registerServer ? registerServer() : HRESULT_FROM_WIN32( ERROR_PROC_NOT_FOUND );
323 FreeLibrary( module );
324
325 if( FAILED( result ) )
326 {
327 aError = wxString::Format( wxS( "Windows preview registration failed (0x%08lx)." ),
328 static_cast<unsigned long>( result ) );
329 return false;
330 }
331
333
336 {
337 aError = repaired.detail;
338 return false;
339 }
340
341 aError.clear();
342 return true;
343}
bool RepairPlatformModelPreviewRegistration(wxString &aError)
MODEL_PREVIEW_STATUS GetPlatformModelPreviewStatus()
MODEL_PREVIEW_STATUS ClassifyWindowsModelPreviewStatus(const WINDOWS_MODEL_PREVIEW_SNAPSHOT &aSnapshot)
MODEL_PREVIEW_ENABLEMENT_STATE enablement
MODEL_PREVIEW_COMPONENT_STATE component
wxString result
Test unit parsing edge cases and error handling.