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#include <paths.h>
24
25#include <wx/filename.h>
26#include <wx/stdpaths.h>
27#include <wx/utils.h>
28
29#include <windows.h>
30#include <winver.h>
31
32#include <array>
33#include <vector>
34
35#pragma comment( lib, "version.lib" )
36
37namespace
38{
39constexpr wchar_t HANDLER_CLSID[] = L"{072BD71D-CB70-4CC8-8F08-D0A763A17C2E}";
40constexpr wchar_t HANDLER_APPID[] = L"{6D2B5079-2F0B-48DD-AB7F-97CEC514D30B}";
41constexpr wchar_t PREVIEW_HANDLER_IID[] = L"{8895B1C6-B41F-4C1C-A562-0D564250836F}";
42constexpr wchar_t REQUIREMENT_ID[] = L"windows-preview-handler-v1";
43constexpr wchar_t HANDLER_NAME[] = L"kicad_3d_model_previewer.dll";
44// Must match fileTypes[] in the preview handler's dllmain.cpp, which writes the registration.
45constexpr std::array<const wchar_t*, 9> EXTENSIONS = { L".stp", L".stpz", L".step", L".stepz", L".igs",
46 L".iges", L".wrl", L".wrz", L".x3d" };
47
48enum class READ_RESULT
49{
50 FOUND,
51 MISSING,
52 READ_ERROR
53};
54
55READ_RESULT ReadRegistryString( HKEY aRoot, const wxString& aKey, const wchar_t* aValueName, wxString& aValue )
56{
57 std::array<wchar_t, 2048> value;
58 DWORD size = static_cast<DWORD>( value.size() * sizeof( wchar_t ) );
59 LONG result = RegGetValueW( aRoot, aKey.wc_str(), aValueName,
60 RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, nullptr,
61 value.data(), &size );
62
63 if( result == ERROR_FILE_NOT_FOUND )
64 return READ_RESULT::MISSING;
65
66 if( result != ERROR_SUCCESS )
67 return READ_RESULT::READ_ERROR;
68
69 aValue = value.data();
70
71 return READ_RESULT::FOUND;
72}
73
74
75READ_RESULT ReadString( const wxString& aKey, const wchar_t* aValueName, wxString& aValue )
76{
77 return ReadRegistryString( HKEY_CURRENT_USER, aKey, aValueName, aValue );
78}
79
80
81READ_RESULT ReadClassString( const wxString& aKey, const wchar_t* aValueName, wxString& aValue )
82{
83 return ReadRegistryString( HKEY_CLASSES_ROOT, aKey, aValueName, aValue );
84}
85
86wxString BundledHandlerPath()
87{
89 {
90 wxFileName fn( wxStandardPaths::Get().GetExecutablePath() );
91 fn.AppendDir( wxS( ".." ) );
92 fn.AppendDir( wxS( "utils" ) );
93 fn.AppendDir( wxS( "preview_handlers" ) );
94 fn.AppendDir( wxS( "kicad_3d_model_preview_handler" ) );
95 fn.SetFullName( HANDLER_NAME );
96
97 return fn.GetFullPath();
98 }
99 else
100 {
101 wxFileName fn( wxStandardPaths::Get().GetExecutablePath() );
102 fn.SetFullName( HANDLER_NAME );
103
104 return fn.GetFullPath();
105 }
106}
107
108wxString ExpandRegistryPath( const wxString& aPath )
109{
110 DWORD required = ExpandEnvironmentStringsW( aPath.wc_str(), nullptr, 0 );
111
112 if( required == 0 )
113 return aPath;
114
115 std::vector<wchar_t> expanded( required );
116
117 if( ExpandEnvironmentStringsW( aPath.wc_str(), expanded.data(), required ) != required )
118 return aPath;
119
120 return expanded.data();
121}
122
123wxString FileVersion( const wxString& aPath )
124{
125 DWORD ignored = 0;
126 DWORD size = GetFileVersionInfoSizeW( aPath.wc_str(), &ignored );
127
128 if( !size )
129 return {};
130
131 std::vector<BYTE> data( size );
132 VS_FIXEDFILEINFO* info = nullptr;
133 UINT infoSize = 0;
134
135 if( !GetFileVersionInfoW( aPath.wc_str(), 0, size, data.data() )
136 || !VerQueryValueW( data.data(), L"\\", reinterpret_cast<void**>( &info ), &infoSize ) || !info )
137 return {};
138
139 return wxString::Format( wxS( "%u.%u.%u.%u" ), HIWORD( info->dwFileVersionMS ), LOWORD( info->dwFileVersionMS ),
140 HIWORD( info->dwFileVersionLS ), LOWORD( info->dwFileVersionLS ) );
141}
142} // namespace
143
144
146{
148 snapshot.requirementId = REQUIREMENT_ID;
149 wxString bundledPath = BundledHandlerPath();
150
151 if( bundledPath.empty() || !wxFileName::FileExists( bundledPath ) )
152 {
153 return ClassifyWindowsModelPreviewStatus( snapshot );
154 }
155
156 snapshot.bundledPresent = true;
157 snapshot.bundledVersion = FileVersion( bundledPath );
158 snapshot.requirementId = wxString::Format( wxS( "%ls-%s" ), REQUIREMENT_ID, snapshot.bundledVersion );
159
160 bool missingAssociation = false;
161 bool foreignOwner = false;
162
163 for( const wchar_t* extension : EXTENSIONS )
164 {
165 wxString owner;
166 wxString mergedKey = wxString::Format( L"%ls\\shellex\\%ls", extension, PREVIEW_HANDLER_IID );
167
168 switch( ReadClassString( mergedKey, nullptr, owner ) )
169 {
170 case READ_RESULT::MISSING:
171 missingAssociation = true;
172 break;
173
174 case READ_RESULT::READ_ERROR:
176 return ClassifyWindowsModelPreviewStatus( snapshot );
177
178 case READ_RESULT::FOUND:
179 foreignOwner = foreignOwner || owner.CmpNoCase( HANDLER_CLSID ) != 0;
180 break;
181 }
182 }
183
184 if( foreignOwner )
185 {
186 snapshot.foreignOwner = true;
187 return ClassifyWindowsModelPreviewStatus( snapshot );
188 }
189
190 wxString registeredPath;
191 wxString inprocKey = wxString::Format( L"Software\\Classes\\CLSID\\%ls\\InprocServer32", HANDLER_CLSID );
192 wxString mergedInprocKey = wxString::Format( L"CLSID\\%ls\\InprocServer32", HANDLER_CLSID );
193 READ_RESULT pathResult = ReadClassString( mergedInprocKey, nullptr, registeredPath );
194
195 if( pathResult == READ_RESULT::READ_ERROR )
196 {
198 return ClassifyWindowsModelPreviewStatus( snapshot );
199 }
200
201 if( pathResult == READ_RESULT::MISSING )
202 {
203 snapshot.module = MODEL_PREVIEW_OBSERVATION::MISSING;
204 return ClassifyWindowsModelPreviewStatus( snapshot );
205 }
206
207 registeredPath = ExpandRegistryPath( registeredPath );
208 wxFileName registeredFile( registeredPath );
209 wxFileName bundledFile( bundledPath );
210 registeredFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_DOTS | wxPATH_NORM_CASE | wxPATH_NORM_LONG );
211 bundledFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_DOTS | wxPATH_NORM_CASE | wxPATH_NORM_LONG );
212
213 if( registeredFile.GetFullPath().CmpNoCase( bundledFile.GetFullPath() ) != 0 )
214 {
215 snapshot.module = MODEL_PREVIEW_OBSERVATION::PRESENT;
216 snapshot.foreignOwner = true;
217 return ClassifyWindowsModelPreviewStatus( snapshot );
218 }
219
220 snapshot.module = MODEL_PREVIEW_OBSERVATION::PRESENT;
221 snapshot.sameModule = true;
222
223 wxString registeredVersion;
224 READ_RESULT registeredVersionResult = ReadString( inprocKey, L"KiCadHandlerVersion", registeredVersion );
225
226 if( registeredVersionResult == READ_RESULT::READ_ERROR )
227 {
229 return ClassifyWindowsModelPreviewStatus( snapshot );
230 }
231
232 snapshot.registeredVersion = registeredVersion;
233
234 for( const wchar_t* extension : EXTENSIONS )
235 {
236 wxString owner;
237 wxString key = wxString::Format( L"Software\\Classes\\%ls\\shellex\\%ls", extension, PREVIEW_HANDLER_IID );
238 READ_RESULT ownerResult = ReadString( key, nullptr, owner );
239
240 if( ownerResult == READ_RESULT::READ_ERROR )
241 {
243 return ClassifyWindowsModelPreviewStatus( snapshot );
244 }
245
246 if( ownerResult == READ_RESULT::FOUND && owner.CmpNoCase( HANDLER_CLSID ) != 0 )
247 {
248 snapshot.foreignOwner = true;
249 return ClassifyWindowsModelPreviewStatus( snapshot );
250 }
251 }
252
253 wxString surrogate;
254 wxString appIdKey = wxString::Format( L"Software\\Classes\\AppID\\%ls", HANDLER_APPID );
255 READ_RESULT appIdResult = ReadString( appIdKey, L"DllSurrogate", surrogate );
256
257 if( appIdResult == READ_RESULT::READ_ERROR )
258 {
260 return ClassifyWindowsModelPreviewStatus( snapshot );
261 }
262
263 wxString clsidAppId;
264 wxString threadingModel;
265 wxString clsidKey = wxString::Format( L"Software\\Classes\\CLSID\\%ls", HANDLER_CLSID );
266 READ_RESULT clsidAppIdResult = ReadString( clsidKey, L"AppID", clsidAppId );
267 READ_RESULT threadingResult = ReadString( inprocKey, L"ThreadingModel", threadingModel );
268
269 if( clsidAppIdResult == READ_RESULT::READ_ERROR || threadingResult == READ_RESULT::READ_ERROR )
270 {
272 return ClassifyWindowsModelPreviewStatus( snapshot );
273 }
274
275 wxString previewHandlerName;
276 READ_RESULT previewListResult = ReadString( wxS( "Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers" ),
277 HANDLER_CLSID, previewHandlerName );
278
279 if( previewListResult == READ_RESULT::READ_ERROR )
280 {
282 return ClassifyWindowsModelPreviewStatus( snapshot );
283 }
284
285 snapshot.associations =
287 snapshot.surrogate = appIdResult == READ_RESULT::FOUND && !surrogate.empty() ? MODEL_PREVIEW_OBSERVATION::PRESENT
289 snapshot.classMetadata = clsidAppIdResult == READ_RESULT::FOUND && threadingResult == READ_RESULT::FOUND
290 && clsidAppId.CmpNoCase( HANDLER_APPID ) == 0
291 && threadingModel.CmpNoCase( wxS( "Apartment" ) ) == 0
294 snapshot.previewList = previewListResult == READ_RESULT::FOUND && !previewHandlerName.empty()
297 return ClassifyWindowsModelPreviewStatus( snapshot );
298}
299
300
302{
304
305 if( !status.canRepairRegistration )
306 {
307 aError = status.detail;
308 return false;
309 }
310
311 wxString handlerPath = BundledHandlerPath();
312 HMODULE module = LoadLibraryExW( handlerPath.wc_str(), nullptr,
313 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS );
314
315 if( !module )
316 {
317 aError = wxString::Format( wxS( "Unable to load the Windows preview handler (error %lu)." ), GetLastError() );
318 return false;
319 }
320
321 using REGISTER_SERVER = HRESULT( STDAPICALLTYPE* )();
322 REGISTER_SERVER registerServer = reinterpret_cast<REGISTER_SERVER>( GetProcAddress( module, "DllRegisterServer" ) );
323 HRESULT result = registerServer ? registerServer() : HRESULT_FROM_WIN32( ERROR_PROC_NOT_FOUND );
324 FreeLibrary( module );
325
326 if( FAILED( result ) )
327 {
328 aError = wxString::Format( wxS( "Windows preview registration failed (0x%08lx)." ),
329 static_cast<unsigned long>( result ) );
330 return false;
331 }
332
334
337 {
338 aError = repaired.detail;
339 return false;
340 }
341
342 aError.clear();
343 return true;
344}
static bool RunningFromBuildDir()
True when KiCad runs from its build tree or KICAD_RUN_FROM_BUILD_DIR is set.
Definition paths.cpp:698
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.