source: smplayer/trunk/src/winfileassoc.cpp

Last change on this file was 188, checked in by Silvan Scherrer, 8 years ago

SMPlayer: update trunk to version 17.1.0

  • Property svn:eol-style set to LF
File size: 14.9 KB
RevLine 
[112]1/* smplayer, GUI front-end for mplayer.
[188]2 Copyright (C) 2006-2017 Ricardo Villalba <rvm@users.sourceforge.net>
[112]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
[135]20 Handles file associations in Windows 7/Vista/XP/2000.
[112]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
[135]26 that we can clean up the registry and restore the old associations for current user.
[112]27
28 Vista:
[135]29 The code can only register the app as default program for selected extensions and check if it is the default.
[112]30 It cannot restore 'old' default application, since this doesn't seem to be possible with the current Vista API.
31
[135]32 Add libole32.a library if compiling with MinGW. In smplayer.pro, under 'win32 {': LIBS += libole32
[112]33
[135]34 Tested on: WinXP, Vista, Win7.
35
[112]36 Author: Florin Braghis (florin@libertv.ro)
37*/
38
39#include "winfileassoc.h"
40#include <QSettings>
41#include <QApplication>
42#include <QFileInfo>
[135]43#include <windows.h>
[112]44
[135]45WinFileAssoc::WinFileAssoc(const QString ClassId, const QString AppName)
[112]46{
[135]47 m_ClassId = ClassId;
48 m_AppName = AppName;
49 m_ClassId2 = QFileInfo(QApplication::applicationFilePath()).fileName();
[112]50}
51
[135]52// Associates all extensions in the fileExtensions list with current app.
53// Returns number of extensions processed successfully.
54int WinFileAssoc::CreateFileAssociations(const QStringList &fileExtensions)
[112]55{
[135]56 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) {
57 return VistaSetAppsAsDefault(fileExtensions);
58 }
[112]59
[135]60 QSettings RegCR("HKEY_CLASSES_ROOT", QSettings::NativeFormat); //Read only on NT+
61 QSettings RegCU("HKEY_CURRENT_USER", QSettings::NativeFormat);
[112]62
[135]63 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
64 return 0;
[112]65
[135]66 if (RegCR.status() != QSettings::NoError)
67 return 0;
[112]68
[135]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 }
[112]75
[135]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;
[112]80
[135]81 QString BackupKeyName = ClassesKeyName + "/" + fileExtension;
82 QString CUKeyName = "Software/Classes/." + fileExtension;
[112]83
[135]84 // Save current ClassId for current user
85 QString KeyVal = RegCU.value(CUKeyName + "/.").toString();
[112]86
[135]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 }
[112]92
[135]93 if (KeyVal != m_ClassId)
94 RegCU.setValue(CUKeyName + "/MPlayer_Backup", KeyVal);
[112]95
[135]96 // Save last ProgId and Application values from the Exts key
97 KeyVal = RegCU.value(ExtKeyName + "/Progid").toString();
[112]98
[135]99 if (KeyVal != m_ClassId && KeyVal != m_ClassId2)
100 RegCU.setValue(ExtKeyName + "/MPlayer_Backup_ProgId", KeyVal);
[112]101
[135]102 KeyVal = RegCU.value(ExtKeyName + "/Application").toString();
[112]103
[135]104 if (KeyVal != m_ClassId || KeyVal != m_ClassId2)
105 RegCU.setValue(ExtKeyName + "/MPlayer_Backup_Application", KeyVal);
[112]106
[135]107 // Create the associations
108 RegCU.setValue(CUKeyName + "/.", m_ClassId); // Extension class
109 RegCU.setValue(ExtKeyName + "/Progid", m_ClassId); // Explorer FileExt association
[112]110
[135]111 if (RegCU.status() == QSettings::NoError && RegCR.status() == QSettings::NoError)
112 count++;
113 }
114
115 return count;
[112]116}
117
[135]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)
[112]122{
[135]123 registeredExtensions.clear();
[112]124
[135]125 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) {
126 return VistaGetDefaultApps(extensionsToCheck, registeredExtensions);
127 }
[112]128
[135]129 QSettings RegCR("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
130 QSettings RegCU("HKEY_CURRENT_USER", QSettings::NativeFormat);
[112]131
[135]132 if (RegCR.status() != QSettings::NoError)
133 return false;
[112]134
[135]135 if (RegCU.status() != QSettings::NoError)
136 return false;
[112]137
[135]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...)
[112]141
[135]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();
[112]145
[135]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 }
[112]156
[135]157 // Finally, check the system-wide association
158 if (!bRegistered)
159 bRegistered = RegCR.value("." + fileExtension + "/.").toString() == m_ClassId;
[112]160
[135]161 if (bRegistered)
162 registeredExtensions.append(fileExtension);
163 }
164
165 return true;
[112]166}
167
[135]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)
[112]172{
[135]173 if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA)
174 return 0; // Not supported by the API
[112]175
[135]176 QSettings RegCR("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
177 QSettings RegCU("HKEY_CURRENT_USER", QSettings::NativeFormat);
[112]178
[135]179 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
180 return 0;
[112]181
[135]182 if (RegCR.status() != QSettings::NoError)
183 return 0;
[112]184
[135]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();
[112]191
[135]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();
[112]197
[135]198 if ((CurProgId == m_ClassId) || (0 == CurProgId.compare(m_ClassId2, Qt::CaseInsensitive))) //Only remove if we own it
199 RegCU.remove(ExtKeyName + "/Progid");
200 }
[112]201
[135]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();
[112]207
[135]208 if ((CurApp == m_ClassId) || (0 == CurApp.compare(m_ClassId2, Qt::CaseInsensitive))) //Only remove if we own it
209 RegCU.remove(ExtKeyName + "/Application");
210 }
[112]211
[135]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;
[112]228}
229
[135]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)
[112]233{
[135]234 QString RootKeyName;
235 QString classId;
[112]236
[135]237 classId = "Software/Classes/" + m_ClassId;
238 RootKeyName = "HKEY_CURRENT_USER";
[112]239
[135]240 QSettings Reg(RootKeyName, QSettings::NativeFormat);
[112]241
[135]242 if (!Reg.isWritable() || Reg.status() != QSettings::NoError)
243 return false;
[112]244
[135]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;
[112]257}
[135]258
259// Remove ClassId from the registry.
260// Called when no associations exist. Note: It's better to do this in the Setup program.
[112]261bool WinFileAssoc::RemoveClassId()
262{
[135]263 QString RootKeyName;
264 QString classId;
[112]265
[135]266 classId = "Software/Classes/" + m_ClassId;
267 RootKeyName = "HKEY_CURRENT_USER";
[112]268
[135]269 QSettings RegCU(RootKeyName, QSettings::NativeFormat);
[112]270
[135]271 if (!RegCU.isWritable() || RegCU.status() != QSettings::NoError)
272 return false;
[112]273
[135]274 RegCU.remove(classId);
275 return true;
[112]276}
277
[135]278// Windows Vista specific implementation
[112]279
280#if !defined(IApplicationAssociationRegistration)
281
[135]282typedef enum tagASSOCIATIONLEVEL {
283 AL_MACHINE,
284 AL_EFFECTIVE,
285 AL_USER
[112]286} ASSOCIATIONLEVEL;
287
[135]288typedef enum tagASSOCIATIONTYPE {
289 AT_FILEEXTENSION,
290 AT_URLPROTOCOL,
291 AT_STARTMENUCLIENT,
292 AT_MIMETYPE
[112]293} ASSOCIATIONTYPE;
294
295MIDL_INTERFACE("4e530b0a-e611-4c77-a3ac-9031d022281b")
[135]296IApplicationAssociationRegistration :
297public IUnknown {
[112]298public:
[135]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;
[112]316};
317#endif
318
[135]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}};
[112]321
[135]322int WinFileAssoc::VistaSetAppsAsDefault(const QStringList &fileExtensions)
[112]323{
[135]324 IApplicationAssociationRegistration *pAAR;
325 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
326 NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg, (void **)&pAAR);
[112]327
[135]328 int count = 0;
[112]329
[135]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;
[112]343}
344
[135]345bool WinFileAssoc::VistaGetDefaultApps(const QStringList &extensions, QStringList &registeredExt)
[112]346{
[135]347 IApplicationAssociationRegistration *pAAR;
[112]348
[135]349 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
350 NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg, (void **)&pAAR);
[112]351
[135]352 if (SUCCEEDED(hr) && (pAAR != NULL)) {
353 foreach(const QString & fileExtension, extensions) {
[165]354 BOOL bIsDefault = false;
[135]355 hr = pAAR->QueryAppIsDefault((const WCHAR *)QString("." + fileExtension).utf16(),
356 AT_FILEEXTENSION,
357 AL_EFFECTIVE,
358 (const WCHAR *)m_AppName.utf16(),
359 &bIsDefault);
[112]360
[135]361 if (SUCCEEDED(hr) && bIsDefault) {
362 registeredExt.append(fileExtension);
363 }
364 }
[112]365
[135]366 pAAR->Release();
367 return true;
368 }
369
370 return false;
[112]371}
Note: See TracBrowser for help on using the repository browser.