source: smplayer/trunk/src/winfileassoc.cpp@ 102

Last change on this file since 102 was 93, checked in by Silvan Scherrer, 15 years ago

smplayer: 0.6.9

File size: 15.5 KB
Line 
1/* smplayer, GUI front-end for mplayer.
2 Copyright (C) 2006-2010 Ricardo Villalba <rvm@escomposlinux.org>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19 Winfileassoc.cpp
20
21 Handles file associations in Windows Vista/XP/2000/NT/ME/98/95.
22 We assume that the code is run without administrator privileges, so the associations are done for current user only.
23 System-wide associations require writing to HKEY_CLASSES_ROOT and we don't want to get our hands dirty with that.
24 Each user on the computer can configure his own set of file associations for SMPlayer, which is extremely cool.
25
26 Optionally, during uninstall, it would be a good idea to call RestoreFileAssociations for all media types so
27 that we can clean up the registry and restore the old associations for current user.
28
29 Vista:
30 The code can only register the app as default program for selected extensions and check if it is the default.
31 It cannot restore 'old' default application, since this doesn't seem to be possible with the current Vista API.
32
33 Tested on: Win98, Win2000, WinXP, Vista.
34 NOT tested on: Win95, ME and NT 4.0 (it should work on 95, ME; Not sure about NT 4.0).
35
36 Author: Florin Braghis (florin@libertv.ro)
37*/
38
39#include "winfileassoc.h"
40#include <QSettings>
41#include <QApplication>
42#include <QFileInfo>
43
44WinFileAssoc::WinFileAssoc( const QString ClassId, const QString AppName )
45{
46 m_ClassId = ClassId;
47 m_AppName = AppName;
48 m_ClassId2 = QFileInfo(QApplication::applicationFilePath()).fileName();
49}
50
51//Associates all extensions in the fileExtensions list with current app.
52//Returns number of extensions processed successfully.
53int WinFileAssoc::CreateFileAssociations(const QStringList& fileExtensions)
54{
55 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA)
56 {
57 return VistaSetAppsAsDefault(fileExtensions);
58 }
59
60 QSettings RegCR ("HKEY_CLASSES_ROOT", QSettings::NativeFormat); //Read only on NT+
61 QSettings RegCU ("HKEY_CURRENT_USER", QSettings::NativeFormat);
62
63 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
64 return 0;
65
66 if (RegCR.status() != QSettings::NoError)
67 return 0;
68
69 if (QSysInfo::WindowsVersion < QSysInfo::WV_NT && !RegCR.isWritable()) //Win98
70 return 0;
71
72 //Check if classId exists in the registry
73 if (!RegCR.contains(m_ClassId) && !RegCU.contains("Software/Classes/" + m_ClassId))
74 {
75 //If doesn't exist (user didn't run the setup program), try to create the ClassId for current user.
76 if (!CreateClassId(QApplication::applicationFilePath(), "SMPlayer Media Player"))
77 return 0;
78 }
79
80 int count = 0;
81 foreach(const QString& fileExtension, fileExtensions)
82 {
83 QString ExtKeyName = QString("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts/.%1").arg(fileExtension);
84 QString ClassesKeyName = m_ClassId;
85
86 QString BackupKeyName = ClassesKeyName + "/" + fileExtension;
87 QString CUKeyName = "Software/Classes/." + fileExtension;
88
89 //Save current ClassId for current user
90 QString KeyVal = RegCU.value(CUKeyName + "/.").toString();
91
92 if (KeyVal.length() == 0 || KeyVal == m_ClassId)
93 {
94 //No registered app for this extension for current user.
95 //Check the system-wide (HKEY_CLASSES_ROOT) ClassId for this extension
96 KeyVal = RegCR.value("." + fileExtension + "/.").toString();
97 }
98
99 if (KeyVal != m_ClassId)
100 RegCU.setValue(CUKeyName + "/MPlayer_Backup", KeyVal);
101
102 //Save last ProgId and Application values from the Exts key
103 KeyVal = RegCU.value(ExtKeyName + "/Progid").toString();
104
105 if (KeyVal != m_ClassId && KeyVal != m_ClassId2)
106 RegCU.setValue(ExtKeyName + "/MPlayer_Backup_ProgId", KeyVal);
107
108 KeyVal = RegCU.value(ExtKeyName + "/Application").toString();
109 if (KeyVal != m_ClassId || KeyVal != m_ClassId2)
110 RegCU.setValue(ExtKeyName + "/MPlayer_Backup_Application", KeyVal);
111
112 //Create the associations
113 if (QSysInfo::WindowsVersion >= QSysInfo::WV_NT)
114 {
115 RegCU.setValue(CUKeyName + "/.", m_ClassId); //Extension class
116 RegCU.setValue(ExtKeyName + "/Progid", m_ClassId); //Explorer FileExt association
117
118 }
119 else
120 {
121 //Windows ME/98/95 support
122 RegCR.setValue("." + fileExtension + "/.", m_ClassId);
123 }
124
125 if (RegCU.status() == QSettings::NoError && RegCR.status() == QSettings::NoError)
126 count++;
127 }
128
129 return count;
130}
131
132//Checks if extensions in extensionsToCheck are registered with this application. Returns a list of registered extensions.
133//Returns false if there was an error accessing the registry.
134//Returns true and 0 elements in registeredExtensions if no extension is associated with current app.
135bool WinFileAssoc::GetRegisteredExtensions( const QStringList& extensionsToCheck, QStringList& registeredExtensions)
136{
137 registeredExtensions.clear();
138
139 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA)
140 {
141 return VistaGetDefaultApps(extensionsToCheck, registeredExtensions);
142 }
143
144 QSettings RegCR ("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
145 QSettings RegCU ("HKEY_CURRENT_USER", QSettings::NativeFormat);
146
147 if (RegCR.status() != QSettings::NoError)
148 return false;
149
150 if (RegCU.status() != QSettings::NoError)
151 return false;
152
153 foreach(const QString& fileExtension, extensionsToCheck)
154 {
155 bool bRegistered = false;
156 //Check the explorer extension (Always use this program to open this kind of file...)
157
158 if (QSysInfo::WindowsVersion >= QSysInfo::WV_NT)
159 {
160 QString FileExtsKey = QString("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts/.%1").arg(fileExtension);
161 QString CurClassId = RegCU.value(FileExtsKey + "/Progid").toString();
162 QString CurAppId = RegCU.value(FileExtsKey + "/Application").toString();
163
164 if (CurClassId.size()) //Registered with Open With... / ProgId ?
165 {
166 bRegistered = (CurClassId == m_ClassId) || (0 == CurClassId.compare(m_ClassId2, Qt::CaseInsensitive));
167 }
168 else
169 if (CurAppId.size())
170 {
171 //If user uses Open With..., explorer creates it's own ClassId under Application, usually "smplayer.exe"
172 bRegistered = (CurAppId == m_ClassId) || (0 == CurAppId.compare(m_ClassId2, Qt::CaseInsensitive));
173 }
174 else
175 {
176 //No classId means that no associations exists in Default Programs or Explorer
177 //Check the default per-user association
178 bRegistered = RegCU.value("Software/Classes/." + fileExtension + "/.").toString() == m_ClassId;
179 }
180 }
181
182 //Finally, check the system-wide association
183 if (!bRegistered)
184 bRegistered = RegCR.value("." + fileExtension + "/.").toString() == m_ClassId;
185
186
187 if (bRegistered)
188 registeredExtensions.append(fileExtension);
189 }
190
191 return true;
192}
193
194//Restores file associations to old defaults (if any) for all extensions in the fileExtensions list.
195//Cleans up our backup keys from the registry.
196//Returns number of extensions successfully processed (error if fileExtensions.count() != return value && count > 0).
197int WinFileAssoc::RestoreFileAssociations(const QStringList& fileExtensions)
198{
199 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA)
200 return 0; //Not supported by the API
201
202 QSettings RegCR ("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
203 QSettings RegCU ("HKEY_CURRENT_USER", QSettings::NativeFormat);
204
205 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
206 return 0;
207
208 if (RegCR.status() != QSettings::NoError)
209 return 0;
210
211 if (QSysInfo::WindowsVersion < QSysInfo::WV_NT && !RegCR.isWritable()) //Win98
212 return 0;
213
214 int count = 0;
215 foreach(const QString& fileExtension, fileExtensions)
216 {
217 QString ExtKeyName = QString("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts/.%1").arg(fileExtension);
218 QString OldProgId = RegCU.value(ExtKeyName + "/MPlayer_Backup_ProgId").toString();
219 QString OldApp = RegCU.value(ExtKeyName + "/MPlayer_Backup_Application").toString();
220 QString OldClassId = RegCU.value("Software/Classes/." + fileExtension + "/MPlayer_Backup").toString();
221
222 //Restore old explorer ProgId
223 if (!OldProgId.isEmpty() && OldProgId != m_ClassId)
224 RegCU.setValue(ExtKeyName + "/Progid", OldProgId);
225 else
226 {
227 QString CurProgId = RegCU.value(ExtKeyName + "/Progid").toString();
228 if ((CurProgId == m_ClassId) || (0 == CurProgId.compare(m_ClassId2, Qt::CaseInsensitive))) //Only remove if we own it
229 RegCU.remove(ExtKeyName + "/Progid");
230 }
231
232 //Restore old explorer Application
233 if (!OldApp.isEmpty() && OldApp != m_ClassId)
234 RegCU.setValue(ExtKeyName + "/Application", OldApp);
235 else
236 {
237 QString CurApp = RegCU.value(ExtKeyName + "/Application").toString();
238 if ((CurApp == m_ClassId) || (0 == CurApp.compare(m_ClassId2, Qt::CaseInsensitive))) //Only remove if we own it
239 RegCU.remove(ExtKeyName + "/Application");
240 }
241
242 if (QSysInfo::WindowsVersion >= QSysInfo::WV_NT)
243 {
244 //Restore old association for current user
245 if (!OldClassId.isEmpty() && OldClassId != m_ClassId)
246 RegCU.setValue("Software/Classes/." + fileExtension + "/.", OldClassId);
247 else
248 {
249 if (RegCU.value("Software/Classes/." + fileExtension + "/.").toString() == m_ClassId) //Only remove if we own it
250 RegCU.remove("Software/Classes/." + fileExtension);
251 }
252 }
253 else
254 {
255 //Windows 98 ==> Write to HKCR
256 if (!OldClassId.isEmpty() && OldClassId != m_ClassId)
257 RegCR.setValue("." + fileExtension + "/.", OldClassId);
258 else
259 {
260 if (RegCR.value("." + fileExtension + "/.").toString() == m_ClassId)
261 RegCR.remove("." + fileExtension);
262 }
263 }
264
265 //Remove our keys:
266 //CurrentUserClasses/.ext/MPlayerBackup
267 //Explorer: Backup_Application and Backup_ProgId
268 RegCU.remove("Software/Classes/." + fileExtension + "/MPlayer_Backup");
269 RegCU.remove(ExtKeyName + "/MPlayer_Backup_Application");
270 RegCU.remove(ExtKeyName + "/MPlayer_Backup_ProgId");
271 }
272 return count;
273}
274
275//Creates a ClassId for current application.
276//Note: It's better to create the classId from the installation program.
277bool WinFileAssoc::CreateClassId(const QString& executablePath, const QString& friendlyName)
278{
279 QString RootKeyName;
280 QString classId;
281
282 if (QSysInfo::WindowsVersion >= QSysInfo::WV_NT)
283 {
284 classId = "Software/Classes/" + m_ClassId;
285 RootKeyName = "HKEY_CURRENT_USER";
286 }
287 else
288 {
289 classId = m_ClassId;
290 RootKeyName = "HKEY_CLASSES_ROOT"; //Windows 95/98/ME
291 }
292
293 QSettings Reg (RootKeyName, QSettings::NativeFormat);
294 if (!Reg.isWritable() || Reg.status() != QSettings::NoError)
295 return false;
296
297 QString appPath = executablePath;
298 appPath.replace('/', '\\'); //Explorer gives 'Access Denied' if we write the path with forward slashes to the registry
299
300 //Add our ProgId to the HKCR classes
301 Reg.setValue(classId + "/shell/open/FriendlyAppName", friendlyName);
302 Reg.setValue(classId + "/shell/open/command/.", QString("\"%1\" \"%2\"").arg(appPath, "%1"));
303 Reg.setValue(classId + "/DefaultIcon/.", QString("\"%1\",1").arg(appPath));
304 //Add "Enqueue" command
305 Reg.setValue(classId + "/shell/enqueue/.", QObject::tr("Enqueue in SMPlayer"));
306 Reg.setValue(classId + "/shell/enqueue/command/.", QString("\"%1\" -add-to-playlist \"%2\"").arg(appPath, "%1"));
307 return true;
308}
309//Remove ClassId from the registry.
310//Called when no associations exist. Note: It's better to do this in the Setup program.
311bool WinFileAssoc::RemoveClassId()
312{
313 QString RootKeyName;
314 QString classId;
315
316 if (QSysInfo::WindowsVersion >= QSysInfo::WV_NT)
317 {
318 classId = "Software/Classes/" + m_ClassId;
319 RootKeyName = "HKEY_CURRENT_USER";
320 }
321 else
322 {
323 classId = m_ClassId;
324 RootKeyName = "HKEY_CLASSES_ROOT"; //Windows 95/98/ME
325 }
326
327 QSettings RegCU (RootKeyName, QSettings::NativeFormat);
328
329 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
330 return false;
331
332 RegCU.remove(classId);
333 return true;
334}
335
336//Windows Vista specific implementation
337//Add libole32.a library if compiling with mingw.
338//In smplayer.pro, under win32{ :
339// LIBS += libole32
340#ifdef WIN32
341#include <windows.h>
342
343#if !defined(IApplicationAssociationRegistration)
344
345typedef enum tagASSOCIATIONLEVEL
346{
347 AL_MACHINE,
348 AL_EFFECTIVE,
349 AL_USER
350} ASSOCIATIONLEVEL;
351
352typedef enum tagASSOCIATIONTYPE
353{
354 AT_FILEEXTENSION,
355 AT_URLPROTOCOL,
356 AT_STARTMENUCLIENT,
357 AT_MIMETYPE
358} ASSOCIATIONTYPE;
359
360MIDL_INTERFACE("4e530b0a-e611-4c77-a3ac-9031d022281b")
361IApplicationAssociationRegistration : public IUnknown
362{
363public:
364 virtual HRESULT STDMETHODCALLTYPE QueryCurrentDefault(LPCWSTR pszQuery,
365 ASSOCIATIONTYPE atQueryType,
366 ASSOCIATIONLEVEL alQueryLevel,
367 LPWSTR *ppszAssociation) = 0;
368 virtual HRESULT STDMETHODCALLTYPE QueryAppIsDefault(LPCWSTR pszQuery,
369 ASSOCIATIONTYPE atQueryType,
370 ASSOCIATIONLEVEL alQueryLevel,
371 LPCWSTR pszAppRegistryName,
372 BOOL *pfDefault) = 0;
373 virtual HRESULT STDMETHODCALLTYPE QueryAppIsDefaultAll(ASSOCIATIONLEVEL alQueryLevel,
374 LPCWSTR pszAppRegistryName,
375 BOOL *pfDefault) = 0;
376 virtual HRESULT STDMETHODCALLTYPE SetAppAsDefault(LPCWSTR pszAppRegistryName,
377 LPCWSTR pszSet,
378 ASSOCIATIONTYPE atSetType) = 0;
379 virtual HRESULT STDMETHODCALLTYPE SetAppAsDefaultAll(LPCWSTR pszAppRegistryName) = 0;
380 virtual HRESULT STDMETHODCALLTYPE ClearUserAssociations( void) = 0;
381};
382#endif
383
384static const CLSID CLSID_ApplicationAssociationReg = {0x591209C7,0x767B,0x42B2,{0x9F,0xBA,0x44,0xEE,0x46,0x15,0xF2,0xC7}};
385static const IID IID_IApplicationAssociationReg = {0x4e530b0a,0xe611,0x4c77,{0xa3,0xac,0x90,0x31,0xd0,0x22,0x28,0x1b}};
386
387int WinFileAssoc::VistaSetAppsAsDefault(const QStringList& fileExtensions)
388{
389 IApplicationAssociationRegistration* pAAR;
390 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
391 NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg, (void**)&pAAR);
392
393 int count = 0;
394 if (SUCCEEDED(hr) && (pAAR != NULL))
395 {
396 foreach(const QString& fileExtension, fileExtensions)
397 {
398 hr = pAAR->SetAppAsDefault((const WCHAR*)m_AppName.utf16(),
399 (const WCHAR*)QString("." + fileExtension).utf16(),
400 AT_FILEEXTENSION);
401
402 if (SUCCEEDED(hr))
403 count++;
404 }
405 pAAR->Release();
406 }
407 return count;
408}
409
410bool WinFileAssoc::VistaGetDefaultApps(const QStringList &extensions, QStringList& registeredExt)
411{
412 IApplicationAssociationRegistration* pAAR;
413
414 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
415 NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg, (void**)&pAAR);
416
417 if (SUCCEEDED(hr) && (pAAR != NULL))
418 {
419 foreach(const QString& fileExtension, extensions)
420 {
421 BOOL bIsDefault = FALSE;
422 hr = pAAR->QueryAppIsDefault((const WCHAR*)QString("." + fileExtension).utf16(),
423 AT_FILEEXTENSION,
424 AL_EFFECTIVE,
425 (const WCHAR*)m_AppName.utf16(),
426 &bIsDefault);
427 if (SUCCEEDED(hr) && bIsDefault)
428 {
429 registeredExt.append(fileExtension);
430 }
431 }
432
433 pAAR->Release();
434 return true;
435 }
436 return false;
437}
438#else
439bool WinFileAssoc::VistaGetDefaultApps(const QStringList &extensions, QStringList& registeredExt)
440{
441 return false;
442}
443
444int WinFileAssoc::VistaSetAppsAsDefault(const QStringList& extensions)
445{
446 return 0;
447}
448#endif
449
Note: See TracBrowser for help on using the repository browser.