msi: Implement the UnregisterFonts standard action.
[wine] / dlls / msi / font.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2004,2005 Aric Stewart for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22 #include "windef.h"
23 #include "winbase.h"
24 #include "winerror.h"
25 #include "winreg.h"
26 #include "wine/debug.h"
27 #include "msipriv.h"
28 #include "wine/unicode.h"
29
30 WINE_DEFAULT_DEBUG_CHANNEL(msi);
31
32 typedef struct _tagTT_OFFSET_TABLE {
33     USHORT uMajorVersion;
34     USHORT uMinorVersion;
35     USHORT uNumOfTables;
36     USHORT uSearchRange;
37     USHORT uEntrySelector;
38     USHORT uRangeShift;
39 } TT_OFFSET_TABLE;
40
41 typedef struct _tagTT_TABLE_DIRECTORY {
42     char szTag[4]; /* table name */
43     ULONG uCheckSum; /* Check sum */
44     ULONG uOffset; /* Offset from beginning of file */
45     ULONG uLength; /* length of the table in bytes */
46 } TT_TABLE_DIRECTORY;
47
48 typedef struct _tagTT_NAME_TABLE_HEADER {
49     USHORT uFSelector; /* format selector. Always 0 */
50     USHORT uNRCount; /* Name Records count */
51     USHORT uStorageOffset; /* Offset for strings storage,
52                             * from start of the table */
53 } TT_NAME_TABLE_HEADER;
54
55 typedef struct _tagTT_NAME_RECORD {
56     USHORT uPlatformID;
57     USHORT uEncodingID;
58     USHORT uLanguageID;
59     USHORT uNameID;
60     USHORT uStringLength;
61     USHORT uStringOffset; /* from start of storage area */
62 } TT_NAME_RECORD;
63
64 #define SWAPWORD(x) MAKEWORD(HIBYTE(x), LOBYTE(x))
65 #define SWAPLONG(x) MAKELONG(SWAPWORD(HIWORD(x)), SWAPWORD(LOWORD(x)))
66
67 static const WCHAR szRegisterFonts[] =
68     {'R','e','g','i','s','t','e','r','F','o','n','t','s',0};
69 static const WCHAR szUnregisterFonts[] =
70     {'U','n','r','e','g','i','s','t','e','r','F','o','n','t','s',0};
71
72 static const WCHAR regfont1[] =
73     {'S','o','f','t','w','a','r','e','\\',
74      'M','i','c','r','o','s','o','f','t','\\',
75      'W','i','n','d','o','w','s',' ','N','T','\\',
76      'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
77      'F','o','n','t','s',0};
78 static const WCHAR regfont2[] =
79     {'S','o','f','t','w','a','r','e','\\',
80      'M','i','c','r','o','s','o','f','t','\\',
81      'W','i','n','d','o','w','s','\\',
82      'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
83      'F','o','n','t','s',0};
84
85 /*
86  * Code based off of code located here
87  * http://www.codeproject.com/gdi/fontnamefromfile.asp
88  *
89  * Using string index 4 (full font name) instead of 1 (family name)
90  */
91 static LPWSTR load_ttfname_from(LPCWSTR filename)
92 {
93     TT_TABLE_DIRECTORY tblDir;
94     BOOL bFound = FALSE;
95     TT_OFFSET_TABLE ttOffsetTable;
96     TT_NAME_TABLE_HEADER ttNTHeader;
97     TT_NAME_RECORD ttRecord;
98     DWORD dwRead;
99     HANDLE handle;
100     LPWSTR ret = NULL;
101     int i;
102
103     handle = CreateFileW(filename ,GENERIC_READ, 0, NULL, OPEN_EXISTING,
104                     FILE_ATTRIBUTE_NORMAL, 0 );
105     if (handle == INVALID_HANDLE_VALUE)
106     {
107         ERR("Unable to open font file %s\n", debugstr_w(filename));
108         return NULL;
109     }
110
111     if (!ReadFile(handle,&ttOffsetTable, sizeof(TT_OFFSET_TABLE),&dwRead,NULL))
112         goto end;
113
114     ttOffsetTable.uNumOfTables = SWAPWORD(ttOffsetTable.uNumOfTables);
115     ttOffsetTable.uMajorVersion = SWAPWORD(ttOffsetTable.uMajorVersion);
116     ttOffsetTable.uMinorVersion = SWAPWORD(ttOffsetTable.uMinorVersion);
117
118     if (ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0)
119         goto end;
120
121     for (i=0; i< ttOffsetTable.uNumOfTables; i++)
122     {
123         if (!ReadFile(handle,&tblDir, sizeof(TT_TABLE_DIRECTORY),&dwRead,NULL))
124             break;
125         if (memcmp(tblDir.szTag,"name",4)==0)
126         {
127             bFound = TRUE;
128             tblDir.uLength = SWAPLONG(tblDir.uLength);
129             tblDir.uOffset = SWAPLONG(tblDir.uOffset);
130             break;
131         }
132     }
133
134     if (!bFound)
135         goto end;
136
137     SetFilePointer(handle, tblDir.uOffset, NULL, FILE_BEGIN);
138     if (!ReadFile(handle,&ttNTHeader, sizeof(TT_NAME_TABLE_HEADER), &dwRead,NULL))
139         goto end;
140
141     ttNTHeader.uNRCount = SWAPWORD(ttNTHeader.uNRCount);
142     ttNTHeader.uStorageOffset = SWAPWORD(ttNTHeader.uStorageOffset);
143     bFound = FALSE;
144     for(i=0; i<ttNTHeader.uNRCount; i++)
145     {
146         if (!ReadFile(handle,&ttRecord, sizeof(TT_NAME_RECORD),&dwRead,NULL))
147             break;
148
149         ttRecord.uNameID = SWAPWORD(ttRecord.uNameID);
150         /* 4 is the Full Font Name */
151         if(ttRecord.uNameID == 4)
152         {
153             int nPos;
154             LPSTR buf;
155             static const char tt[] = " (TrueType)";
156
157             ttRecord.uStringLength = SWAPWORD(ttRecord.uStringLength);
158             ttRecord.uStringOffset = SWAPWORD(ttRecord.uStringOffset);
159             nPos = SetFilePointer(handle, 0, NULL, FILE_CURRENT);
160             SetFilePointer(handle, tblDir.uOffset +
161                             ttRecord.uStringOffset +
162                             ttNTHeader.uStorageOffset,
163                             NULL, FILE_BEGIN);
164             buf = msi_alloc_zero( ttRecord.uStringLength + 1 + strlen(tt) );
165             ReadFile(handle, buf, ttRecord.uStringLength, &dwRead, NULL);
166             if (strlen(buf) > 0)
167             {
168                 strcat(buf,tt);
169                 ret = strdupAtoW(buf);
170                 msi_free(buf);
171                 break;
172             }
173
174             msi_free(buf);
175             SetFilePointer(handle,nPos, NULL, FILE_BEGIN);
176         }
177     }
178
179 end:
180     CloseHandle(handle);
181
182     TRACE("Returning fontname %s\n",debugstr_w(ret));
183     return ret;
184 }
185
186 static UINT ITERATE_RegisterFonts(MSIRECORD *row, LPVOID param)
187 {
188     MSIPACKAGE *package = param;
189     LPWSTR name;
190     LPCWSTR filename;
191     MSIFILE *file;
192     HKEY hkey1, hkey2;
193     MSIRECORD *uirow;
194     LPWSTR uipath, p;
195
196     filename = MSI_RecordGetString( row, 1 );
197     file = get_loaded_file( package, filename );
198     if (!file)
199     {
200         ERR("Unable to load file\n");
201         return ERROR_SUCCESS;
202     }
203
204     /* check to make sure that component is installed */
205     if (!ACTION_VerifyComponentForAction( file->Component, INSTALLSTATE_LOCAL))
206     {
207         TRACE("Skipping: Component not scheduled for install\n");
208         return ERROR_SUCCESS;
209     }
210
211     RegCreateKeyW(HKEY_LOCAL_MACHINE,regfont1,&hkey1);
212     RegCreateKeyW(HKEY_LOCAL_MACHINE,regfont2,&hkey2);
213
214     if (MSI_RecordIsNull(row,2))
215         name = load_ttfname_from( file->TargetPath );
216     else
217         name = msi_dup_record_field(row,2);
218
219     if (name)
220     {
221         msi_reg_set_val_str( hkey1, name, file->TargetPath);
222         msi_reg_set_val_str( hkey2, name, file->TargetPath);
223     }
224
225     msi_free(name);
226     RegCloseKey(hkey1);
227     RegCloseKey(hkey2);
228
229     /* the UI chunk */
230     uirow = MSI_CreateRecord( 1 );
231     uipath = strdupW( file->TargetPath );
232     p = strrchrW(uipath,'\\');
233     if (p) p++;
234     else p = uipath;
235     MSI_RecordSetStringW( uirow, 1, p );
236     ui_actiondata( package, szRegisterFonts, uirow);
237     msiobj_release( &uirow->hdr );
238     msi_free( uipath );
239     /* FIXME: call ui_progress? */
240
241     return ERROR_SUCCESS;
242 }
243
244 UINT ACTION_RegisterFonts(MSIPACKAGE *package)
245 {
246     UINT rc;
247     MSIQUERY * view;
248     static const WCHAR ExecSeqQuery[] =
249         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
250          '`','F','o','n','t','`',0};
251
252     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
253     if (rc != ERROR_SUCCESS)
254     {
255         TRACE("MSI_DatabaseOpenViewW failed: %d\n", rc);
256         return ERROR_SUCCESS;
257     }
258
259     MSI_IterateRecords(view, NULL, ITERATE_RegisterFonts, package);
260     msiobj_release(&view->hdr);
261
262     return ERROR_SUCCESS;
263 }
264
265 static UINT ITERATE_UnregisterFonts( MSIRECORD *row, LPVOID param )
266 {
267     MSIPACKAGE *package = param;
268     LPWSTR name;
269     LPCWSTR filename;
270     MSIFILE *file;
271     HKEY hkey1, hkey2;
272     MSIRECORD *uirow;
273     LPWSTR uipath, p;
274
275     filename = MSI_RecordGetString( row, 1 );
276     file = get_loaded_file( package, filename );
277     if (!file)
278     {
279         ERR("Unable to load file\n");
280         return ERROR_SUCCESS;
281     }
282
283     /* check to make sure that component is uninstalled */
284     if (!ACTION_VerifyComponentForAction( file->Component, INSTALLSTATE_ABSENT ))
285     {
286         TRACE("Skipping: Component not scheduled for uninstall\n");
287         return ERROR_SUCCESS;
288     }
289
290     RegCreateKeyW( HKEY_LOCAL_MACHINE, regfont1, &hkey1 );
291     RegCreateKeyW( HKEY_LOCAL_MACHINE, regfont2, &hkey2 );
292
293     if (MSI_RecordIsNull( row, 2 ))
294         name = load_ttfname_from( file->TargetPath );
295     else
296         name = msi_dup_record_field( row, 2 );
297
298     if (name)
299     {
300         RegDeleteValueW( hkey1, name );
301         RegDeleteValueW( hkey2, name );
302     }
303
304     msi_free( name );
305     RegCloseKey( hkey1 );
306     RegCloseKey( hkey2 );
307
308     /* the UI chunk */
309     uirow = MSI_CreateRecord( 1 );
310     uipath = strdupW( file->TargetPath );
311     p = strrchrW( uipath,'\\' );
312     if (p) p++;
313     else p = uipath;
314     MSI_RecordSetStringW( uirow, 1, p );
315     ui_actiondata( package, szUnregisterFonts, uirow );
316     msiobj_release( &uirow->hdr );
317     msi_free( uipath );
318     /* FIXME: call ui_progress? */
319
320     return ERROR_SUCCESS;
321 }
322
323 UINT ACTION_UnregisterFonts( MSIPACKAGE *package )
324 {
325     UINT r;
326     MSIQUERY *view;
327     static const WCHAR query[] =
328         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
329          '`','F','o','n','t','`',0};
330
331     r = MSI_DatabaseOpenViewW( package->db, query, &view );
332     if (r != ERROR_SUCCESS)
333     {
334         TRACE("MSI_DatabaseOpenViewW failed: %u\n", r);
335         return ERROR_SUCCESS;
336     }
337
338     MSI_IterateRecords( view, NULL, ITERATE_UnregisterFonts, package );
339     msiobj_release( &view->hdr );
340
341     return ERROR_SUCCESS;
342 }