1 /*  smplayer, GUI front-end for mplayer.
2     Copyright (C) 2006-2014 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 /*
40    main changes compared to SMPlayer:
41      replaced SMPlayer by Qmmp
42      added icon cache update
43      removed xp support
44 */
45 
46 
47 #include "winfileassoc.h"
48 #include <QSettings>
49 #include <QApplication>
50 #include <QFileInfo>
51 #include <windows.h>
52 #include <shlobj.h>
53 
54 #ifndef SHCNE_ASSOCHANGED
55 #define SHCNE_ASSOCHANGED __MSABI_LONG(0x08000000)
56 #endif
57 
WinFileAssoc(const QString AppName)58 WinFileAssoc::WinFileAssoc(const QString AppName) : m_AppName(AppName)
59 {}
60 
61 // Associates all extensions in the fileExtensions list with current app.
62 // Returns number of extensions processed successfully.
CreateFileAssociations(const QStringList & fileExtensions)63 int WinFileAssoc::CreateFileAssociations(const QStringList &fileExtensions)
64 {
65     return VistaSetAppsAsDefault(fileExtensions);
66 }
67 
68 // Checks if extensions in extensionsToCheck are registered with this application. Returns a list of registered extensions.
69 // Returns false if there was an error accessing the registry.
70 // Returns true and 0 elements in registeredExtensions if no extension is associated with current app.
GetRegisteredExtensions(const QStringList & extensionsToCheck,QStringList & registeredExtensions)71 bool WinFileAssoc::GetRegisteredExtensions(const QStringList &extensionsToCheck, QStringList &registeredExtensions)
72 {
73     registeredExtensions.clear();
74     return VistaGetDefaultApps(extensionsToCheck, registeredExtensions);
75 }
76 
77 // Windows Vista specific implementation
78 
79 #if !defined(IApplicationAssociationRegistration)
80 
81 typedef enum tagASSOCIATIONLEVEL {
82     AL_MACHINE,
83     AL_EFFECTIVE,
84     AL_USER
85 } ASSOCIATIONLEVEL;
86 
87 typedef enum tagASSOCIATIONTYPE {
88     AT_FILEEXTENSION,
89     AT_URLPROTOCOL,
90     AT_STARTMENUCLIENT,
91     AT_MIMETYPE
92 } ASSOCIATIONTYPE;
93 
94 MIDL_INTERFACE("4e530b0a-e611-4c77-a3ac-9031d022281b")
95 IApplicationAssociationRegistration :
96 public IUnknown {
97 public:
98     virtual HRESULT STDMETHODCALLTYPE QueryCurrentDefault(LPCWSTR pszQuery,
99     ASSOCIATIONTYPE atQueryType,
100     ASSOCIATIONLEVEL alQueryLevel,
101     LPWSTR * ppszAssociation) = 0;
102     virtual HRESULT STDMETHODCALLTYPE QueryAppIsDefault(LPCWSTR pszQuery,
103     ASSOCIATIONTYPE atQueryType,
104     ASSOCIATIONLEVEL alQueryLevel,
105     LPCWSTR pszAppRegistryName,
106     BOOL * pfDefault) = 0;
107     virtual HRESULT STDMETHODCALLTYPE QueryAppIsDefaultAll(ASSOCIATIONLEVEL alQueryLevel,
108     LPCWSTR pszAppRegistryName,
109     BOOL * pfDefault) = 0;
110     virtual HRESULT STDMETHODCALLTYPE SetAppAsDefault(LPCWSTR pszAppRegistryName,
111     LPCWSTR pszSet,
112     ASSOCIATIONTYPE atSetType) = 0;
113     virtual HRESULT STDMETHODCALLTYPE SetAppAsDefaultAll(LPCWSTR pszAppRegistryName) = 0;
114     virtual HRESULT STDMETHODCALLTYPE ClearUserAssociations(void) = 0;
115 };
116 #endif
117 
118 static const CLSID CLSID_ApplicationAssociationReg = {0x591209C7, 0x767B, 0x42B2, {0x9F, 0xBA, 0x44, 0xEE, 0x46, 0x15, 0xF2, 0xC7}};
119 static const IID   IID_IApplicationAssociationReg  = {0x4e530b0a, 0xe611, 0x4c77, {0xa3, 0xac, 0x90, 0x31, 0xd0, 0x22, 0x28, 0x1b}};
120 
121 int WinFileAssoc::VistaSetAppsAsDefault(const QStringList &fileExtensions)
122 {
123     IApplicationAssociationRegistration *pAAR;
124     HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
125                                   NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg,	(void **)&pAAR);
126 
127     int count = 0;
128 
129     if (SUCCEEDED(hr) && (pAAR != NULL)) {
130         for(const QString &fileExtension : qAsConst(fileExtensions)) {
131             hr = pAAR->SetAppAsDefault((const WCHAR *)m_AppName.utf16(),
132                                        (const WCHAR *)QString("." + fileExtension).utf16(),
133                                        AT_FILEEXTENSION);
134 
135             if (SUCCEEDED(hr))
136                 count++;
137         }
138         pAAR->Release();
139     }
140 
141     return count;
142 }
143 
144 bool WinFileAssoc::VistaGetDefaultApps(const QStringList &extensions, QStringList &registeredExt)
145 {
146     IApplicationAssociationRegistration *pAAR;
147 
148     HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationReg,
149                                   NULL, CLSCTX_INPROC, IID_IApplicationAssociationReg,	(void **)&pAAR);
150 
151     if (SUCCEEDED(hr) && (pAAR != NULL)) {
152         for(const QString &fileExtension : qAsConst(extensions)) {
153             BOOL bIsDefault = false;
154             hr = pAAR->QueryAppIsDefault((const WCHAR *)QString("." + fileExtension).utf16(),
155                                          AT_FILEEXTENSION,
156                                          AL_EFFECTIVE,
157                                          (const WCHAR *)m_AppName.utf16(),
158                                          &bIsDefault);
159 
160             if (SUCCEEDED(hr) && bIsDefault) {
161                 registeredExt.append(fileExtension);
162             }
163         }
164 
165         pAAR->Release();
166         return true;
167     }
168 
169     return false;
170 }
171