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

Last change on this file since 176 was 176, checked in by Silvan Scherrer, 9 years ago

smplayer: update trunk to version 16.4

  • Property svn:eol-style set to LF
File size: 14.9 KB
Line 
1/* smplayer, GUI front-end for mplayer.
2 Copyright (C) 2006-2016 Ricardo Villalba <rvm@users.sourceforge.net>
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 Winfileassoc.cpp
19
20 Handles file associations in Windows 7/Vista/XP/2000.
21 We assume that the code is run without administrator privileges, so the associations are done for current user only.
22 System-wide associations require writing to HKEY_CLASSES_ROOT and we don't want to get our hands dirty with that.
23 Each user on the computer can configure his own set of file associations for SMPlayer, which is extremely cool.
24
25 Optionally, during uninstall, it would be a good idea to call RestoreFileAssociations for all media types so
26 that we can clean up the registry and restore the old associations for current user.
27
28 Vista:
29 The code can only register the app as default program for selected extensions and check if it is the default.
30 It cannot restore 'old' default application, since this doesn't seem to be possible with the current Vista API.
31
32 Add libole32.a library if compiling with MinGW. In smplayer.pro, under 'win32 {': LIBS += libole32
33
34 Tested on: WinXP, Vista, Win7.
35
36 Author: Florin Braghis (florin@libertv.ro)
37*/
38
39#include "winfileassoc.h"
40#include <QSettings>
41#include <QApplication>
42#include <QFileInfo>
43#include <windows.h>
44
45WinFileAssoc::WinFileAssoc(const QString ClassId, const QString AppName)
46{
47 m_ClassId = ClassId;
48 m_AppName = AppName;
49 m_ClassId2 = QFileInfo(QApplication::applicationFilePath()).fileName();
50}
51
52// Associates all extensions in the fileExtensions list with current app.
53// Returns number of extensions processed successfully.
54int WinFileAssoc::CreateFileAssociations(const QStringList &fileExtensions)
55{
56 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) {
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 // Check if classId exists in the registry
70 if (!RegCR.contains(m_ClassId) && !RegCU.contains("Software/Classes/" + m_ClassId)) {
71 // If doesn't exist (user didn't run the setup program), try to create the ClassId for current user.
72 if (!CreateClassId(QApplication::applicationFilePath(), "SMPlayer Media Player"))
73 return 0;
74 }
75
76 int count = 0;
77 foreach(const QString & fileExtension, fileExtensions) {
78 QString ExtKeyName = QString("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts/.%1").arg(fileExtension);
79 QString ClassesKeyName = m_ClassId;
80
81 QString BackupKeyName = ClassesKeyName + "/" + fileExtension;
82 QString CUKeyName = "Software/Classes/." + fileExtension;
83
84 // Save current ClassId for current user
85 QString KeyVal = RegCU.value(CUKeyName + "/.").toString();
86
87 if (KeyVal.length() == 0 || KeyVal == m_ClassId) {
88 // No registered app for this extension for current user.
89 // Check the system-wide (HKEY_CLASSES_ROOT) ClassId for this extension
90 KeyVal = RegCR.value("." + fileExtension + "/.").toString();
91 }
92
93 if (KeyVal != m_ClassId)
94 RegCU.setValue(CUKeyName + "/MPlayer_Backup", KeyVal);
95
96 // Save last ProgId and Application values from the Exts key
97 KeyVal = RegCU.value(ExtKeyName + "/Progid").toString();
98
99 if (KeyVal != m_ClassId && KeyVal != m_ClassId2)
100 RegCU.setValue(ExtKeyName + "/MPlayer_Backup_ProgId", KeyVal);
101
102 KeyVal = RegCU.value(ExtKeyName + "/Application").toString();
103
104 if (KeyVal != m_ClassId || KeyVal != m_ClassId2)
105 RegCU.setValue(ExtKeyName + "/MPlayer_Backup_Application", KeyVal);
106
107 // Create the associations
108 RegCU.setValue(CUKeyName + "/.", m_ClassId); // Extension class
109 RegCU.setValue(ExtKeyName + "/Progid", m_ClassId); // Explorer FileExt association
110
111 if (RegCU.status() == QSettings::NoError && RegCR.status() == QSettings::NoError)
112 count++;
113 }
114
115 return count;
116}
117
118// Checks if extensions in extensionsToCheck are registered with this application. Returns a list of registered extensions.
119// Returns false if there was an error accessing the registry.
120// Returns true and 0 elements in registeredExtensions if no extension is associated with current app.
121bool WinFileAssoc::GetRegisteredExtensions(const QStringList &extensionsToCheck, QStringList &registeredExtensions)
122{
123 registeredExtensions.clear();
124
125 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) {
126 return VistaGetDefaultApps(extensionsToCheck, registeredExtensions);
127 }
128
129 QSettings RegCR("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
130 QSettings RegCU("HKEY_CURRENT_USER", QSettings::NativeFormat);
131
132 if (RegCR.status() != QSettings::NoError)
133 return false;
134
135 if (RegCU.status() != QSettings::NoError)
136 return false;
137
138 foreach(const QString & fileExtension, extensionsToCheck) {
139 bool bRegistered = false;
140 // Check the Explorer extension (Always use this program to open this kind of file...)
141
142 QString FileExtsKey = QString("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts/.%1").arg(fileExtension);
143 QString CurClassId = RegCU.value(FileExtsKey + "/Progid").toString();
144 QString CurAppId = RegCU.value(FileExtsKey + "/Application").toString();
145
146 if (CurClassId.size()) { // Registered with Open With... / ProgId ?
147 bRegistered = (CurClassId == m_ClassId) || (0 == CurClassId.compare(m_ClassId2, Qt::CaseInsensitive));
148 } else if (CurAppId.size()) {
149 // If user uses Open With..., explorer creates it's own ClassId under Application, usually "smplayer.exe"
150 bRegistered = (CurAppId == m_ClassId) || (0 == CurAppId.compare(m_ClassId2, Qt::CaseInsensitive));
151 } else {
152 // No classId means that no associations exists in Default Programs or Explorer
153 // Check the default per-user association
154 bRegistered = RegCU.value("Software/Classes/." + fileExtension + "/.").toString() == m_ClassId;
155 }
156
157 // Finally, check the system-wide association
158 if (!bRegistered)
159 bRegistered = RegCR.value("." + fileExtension + "/.").toString() == m_ClassId;
160
161 if (bRegistered)
162 registeredExtensions.append(fileExtension);
163 }
164
165 return true;
166}
167
168// Restores file associations to old defaults (if any) for all extensions in the fileExtensions list.
169// Cleans up our backup keys from the registry.
170// Returns number of extensions successfully processed (error if fileExtensions.count() != return value && count > 0).
171int WinFileAssoc::RestoreFileAssociations(const QStringList &fileExtensions)
172{
173 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA)
174 return 0; // Not supported by the API
175
176 QSettings RegCR("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
177 QSettings RegCU("HKEY_CURRENT_USER", QSettings::NativeFormat);
178
179 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
180 return 0;
181
182 if (RegCR.status() != QSettings::NoError)
183 return 0;
184
185 int count = 0;
186 foreach(const QString & fileExtension, fileExtensions) {
187 QString ExtKeyName = QString("Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts/.%1").arg(fileExtension);
188 QString OldProgId = RegCU.value(ExtKeyName + "/MPlayer_Backup_ProgId").toString();
189 QString OldApp = RegCU.value(ExtKeyName + "/MPlayer_Backup_Application").toString();
190 QString OldClassId = RegCU.value("Software/Classes/." + fileExtension + "/MPlayer_Backup").toString();
191
192 // Restore old explorer ProgId
193 if (!OldProgId.isEmpty() && OldProgId != m_ClassId)
194 RegCU.setValue(ExtKeyName + "/Progid", OldProgId);
195 else {
196 QString CurProgId = RegCU.value(ExtKeyName + "/Progid").toString();
197
198 if ((CurProgId == m_ClassId) || (0 == CurProgId.compare(m_ClassId2, Qt::CaseInsensitive))) //Only remove if we own it
199 RegCU.remove(ExtKeyName + "/Progid");
200 }
201
202 // Restore old explorer Application
203 if (!OldApp.isEmpty() && OldApp != m_ClassId)
204 RegCU.setValue(ExtKeyName + "/Application", OldApp);
205 else {
206 QString CurApp = RegCU.value(ExtKeyName + "/Application").toString();
207
208 if ((CurApp == m_ClassId) || (0 == CurApp.compare(m_ClassId2, Qt::CaseInsensitive))) //Only remove if we own it
209 RegCU.remove(ExtKeyName + "/Application");
210 }
211
212 // Restore old association for current user
213 if (!OldClassId.isEmpty() && OldClassId != m_ClassId)
214 RegCU.setValue("Software/Classes/." + fileExtension + "/.", OldClassId);
215 else {
216 if (RegCU.value("Software/Classes/." + fileExtension + "/.").toString() == m_ClassId) //Only remove if we own it
217 RegCU.remove("Software/Classes/." + fileExtension);
218 }
219
220 // Remove our keys:
221 // CurrentUserClasses/.ext/MPlayerBackup
222 // Explorer: Backup_Application and Backup_ProgId
223 RegCU.remove("Software/Classes/." + fileExtension + "/MPlayer_Backup");
224 RegCU.remove(ExtKeyName + "/MPlayer_Backup_Application");
225 RegCU.remove(ExtKeyName + "/MPlayer_Backup_ProgId");
226 }
227 return count;
228}
229
230// Creates a ClassId for current application.
231// Note: It's better to create the classId from the installation program.
232bool WinFileAssoc::CreateClassId(const QString &executablePath, const QString &friendlyName)
233{
234 QString RootKeyName;
235 QString classId;
236
237 classId = "Software/Classes/" + m_ClassId;
238 RootKeyName = "HKEY_CURRENT_USER";
239
240 QSettings Reg(RootKeyName, QSettings::NativeFormat);
241
242 if (!Reg.isWritable() || Reg.status() != QSettings::NoError)
243 return false;
244
245 QString appPath = executablePath;
246 appPath.replace('/', '\\'); // Explorer gives 'Access Denied' if we write the path with forward slashes to the registry
247
248 // Add our ProgId to the HKCR classes
249 Reg.setValue(classId + "/shell/open/FriendlyAppName", friendlyName);
250 Reg.setValue(classId + "/shell/open/command/.", QString("\"%1\" \"%2\"").arg(appPath, "%1"));
251 Reg.setValue(classId + "/DefaultIcon/.", QString("\"%1\",1").arg(appPath));
252
253 // Add "Enqueue" command
254 Reg.setValue(classId + "/shell/enqueue/.", QObject::tr("Enqueue in SMPlayer"));
255 Reg.setValue(classId + "/shell/enqueue/command/.", QString("\"%1\" -add-to-playlist \"%2\"").arg(appPath, "%1"));
256 return true;
257}
258
259// Remove ClassId from the registry.
260// Called when no associations exist. Note: It's better to do this in the Setup program.
261bool WinFileAssoc::RemoveClassId()
262{
263 QString RootKeyName;
264 QString classId;
265
266 classId = "Software/Classes/" + m_ClassId;
267 RootKeyName = "HKEY_CURRENT_USER";
268
269 QSettings RegCU(RootKeyName, QSettings::NativeFormat);
270
271 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
272 return false;
273
274 RegCU.remove(classId);
275 return true;
276}
277
278// Windows Vista specific implementation
279
280#if !defined(IApplicationAssociationRegistration)
281
282typedef enum tagASSOCIATIONLEVEL {
283 AL_MACHINE,
284 AL_EFFECTIVE,
285 AL_USER
286} ASSOCIATIONLEVEL;
287
288typedef enum tagASSOCIATIONTYPE {
289 AT_FILEEXTENSION,
290 AT_URLPROTOCOL,
291 AT_STARTMENUCLIENT,
292 AT_MIMETYPE
293} ASSOCIATIONTYPE;
294
295MIDL_INTERFACE("4e530b0a-e611-4c77-a3ac-9031d022281b")
296IApplicationAssociationRegistration :
297public IUnknown {
298public:
299 virtual HRESULT STDMETHODCALLTYPE QueryCurrentDefault(LPCWSTR pszQuery,
300 ASSOCIATIONTYPE atQueryType,
301 ASSOCIATIONLEVEL alQueryLevel,
302 LPWSTR * ppszAssociation) = 0;
303 virtual HRESULT STDMETHODCALLTYPE QueryAppIsDefault(LPCWSTR pszQuery,
304 ASSOCIATIONTYPE atQueryType,
305 ASSOCIATIONLEVEL alQueryLevel,
306 LPCWSTR pszAppRegistryName,
307 BOOL * pfDefault) = 0;
308 virtual HRESULT STDMETHODCALLTYPE QueryAppIsDefaultAll(ASSOCIATIONLEVEL alQueryLevel,
309 LPCWSTR pszAppRegistryName,
310 BOOL * pfDefault) = 0;
311 virtual HRESULT STDMETHODCALLTYPE SetAppAsDefault(LPCWSTR pszAppRegistryName,
312 LPCWSTR pszSet,
313 ASSOCIATIONTYPE atSetType) = 0;
314 virtual HRESULT STDMETHODCALLTYPE SetAppAsDefaultAll(LPCWSTR pszAppRegistryName) = 0;
315 virtual HRESULT STDMETHODCALLTYPE ClearUserAssociations(void) = 0;
316};
317#endif
318
319static const CLSID CLSID_ApplicationAssociationReg = {0x591209C7, 0x767B, 0x42B2, {0x9F, 0xBA, 0x44, 0xEE, 0x46, 0x15, 0xF2, 0xC7}};
320static const IID IID_IApplicationAssociationReg = {0x4e530b0a, 0xe611, 0x4c77, {0xa3, 0xac, 0x90, 0x31, 0xd0, 0x22, 0x28, 0x1b}};
321
322int WinFileAssoc::VistaSetAppsAsDefault(const QStringList &fileExtensions)
323{
324 IApplicationAssociationRegistration *pAAR;
325 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
326 NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg, (void **)&pAAR);
327
328 int count = 0;
329
330 if (SUCCEEDED(hr) && (pAAR != NULL)) {
331 foreach(const QString & fileExtension, fileExtensions) {
332 hr = pAAR->SetAppAsDefault((const WCHAR *)m_AppName.utf16(),
333 (const WCHAR *)QString("." + fileExtension).utf16(),
334 AT_FILEEXTENSION);
335
336 if (SUCCEEDED(hr))
337 count++;
338 }
339 pAAR->Release();
340 }
341
342 return count;
343}
344
345bool WinFileAssoc::VistaGetDefaultApps(const QStringList &extensions, QStringList &registeredExt)
346{
347 IApplicationAssociationRegistration *pAAR;
348
349 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
350 NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg, (void **)&pAAR);
351
352 if (SUCCEEDED(hr) && (pAAR != NULL)) {
353 foreach(const QString & fileExtension, extensions) {
354 BOOL bIsDefault = false;
355 hr = pAAR->QueryAppIsDefault((const WCHAR *)QString("." + fileExtension).utf16(),
356 AT_FILEEXTENSION,
357 AL_EFFECTIVE,
358 (const WCHAR *)m_AppName.utf16(),
359 &bIsDefault);
360
361 if (SUCCEEDED(hr) && bIsDefault) {
362 registeredExt.append(fileExtension);
363 }
364 }
365
366 pAAR->Release();
367 return true;
368 }
369
370 return false;
371}
Note: See TracBrowser for help on using the repository browser.