winex11: Get rid of the GetPixel implementation, use the null driver fallback instead.
[wine] / dlls / gdi.exe16 / printdrv.c
1 /*
2  * Implementation of some printer driver bits
3  *
4  * Copyright 1996 John Harvey
5  * Copyright 1998 Huw Davies
6  * Copyright 1998 Andreas Mohr
7  * Copyright 1999 Klaas van Gend
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23
24 #include "config.h"
25 #include "wine/port.h"
26
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <signal.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #ifdef HAVE_IO_H
35 # include <io.h>
36 #endif
37 #ifdef HAVE_SYS_WAIT_H
38 # include <sys/wait.h>
39 #endif
40 #ifdef HAVE_UNISTD_H
41 # include <unistd.h>
42 #endif
43 #include <fcntl.h>
44 #include "windef.h"
45 #include "winbase.h"
46 #include "winuser.h"
47 #include "wine/winbase16.h"
48 #include "wine/wingdi16.h"
49 #include "winspool.h"
50 #include "winerror.h"
51 #include "winreg.h"
52 #include "wownt32.h"
53 #include "wine/debug.h"
54
55 WINE_DEFAULT_DEBUG_CHANNEL(print);
56
57 static const char PrinterModel[]      = "Printer Model";
58 static const char DefaultDevMode[]    = "Default DevMode";
59 static const char PrinterDriverData[] = "PrinterDriverData";
60 static const char Printers[]          = "System\\CurrentControlSet\\Control\\Print\\Printers\\";
61
62 /****************** misc. printer related functions */
63
64 /*
65  * The following function should implement a queing system
66  */
67 struct hpq
68 {
69     struct hpq  *next;
70     int          tag;
71     int          key;
72 };
73
74 static struct hpq *hpqueue;
75
76 /**********************************************************************
77  *           CreatePQ   (GDI.230)
78  *
79  */
80 HPQ16 WINAPI CreatePQ16(INT16 size)
81 {
82 #if 0
83     HGLOBAL16 hpq = 0;
84     WORD tmp_size;
85     LPWORD pPQ;
86
87     tmp_size = size << 2;
88     if (!(hpq = GlobalAlloc16(GMEM_SHARE|GMEM_MOVEABLE, tmp_size + 8)))
89        return 0xffff;
90     pPQ = GlobalLock16(hpq);
91     *pPQ++ = 0;
92     *pPQ++ = tmp_size;
93     *pPQ++ = 0;
94     *pPQ++ = 0;
95     GlobalUnlock16(hpq);
96
97     return (HPQ16)hpq;
98 #else
99     FIXME("(%d): stub\n",size);
100     return 1;
101 #endif
102 }
103
104 /**********************************************************************
105  *           DeletePQ   (GDI.235)
106  *
107  */
108 INT16 WINAPI DeletePQ16(HPQ16 hPQ)
109 {
110     return GlobalFree16(hPQ);
111 }
112
113 /**********************************************************************
114  *           ExtractPQ   (GDI.232)
115  *
116  */
117 INT16 WINAPI ExtractPQ16(HPQ16 hPQ)
118 {
119     struct hpq *queue, *prev, *current, *currentPrev;
120     int key = 0, tag = -1;
121     prev = NULL;
122     queue = current = hpqueue;
123     if (current)
124         key = current->key;
125
126     while (current)
127     {
128         currentPrev = current;
129         current = current->next;
130         if (current)
131         {
132             if (current->key < key)
133             {
134                 queue = current;
135                 prev = currentPrev;
136             }
137         }
138     }
139     if (queue)
140     {
141         tag = queue->tag;
142
143         if (prev)
144             prev->next = queue->next;
145         else
146             hpqueue = queue->next;
147         HeapFree(GetProcessHeap(), 0, queue);
148     }
149
150     TRACE("%x got tag %d key %d\n", hPQ, tag, key);
151
152     return tag;
153 }
154
155 /**********************************************************************
156  *           InsertPQ   (GDI.233)
157  *
158  */
159 INT16 WINAPI InsertPQ16(HPQ16 hPQ, INT16 tag, INT16 key)
160 {
161     struct hpq *queueItem = HeapAlloc(GetProcessHeap(), 0, sizeof(struct hpq));
162     if(queueItem == NULL) {
163         ERR("Memory exausted!\n");
164         return FALSE;
165     }
166     queueItem->next = hpqueue;
167     hpqueue = queueItem;
168     queueItem->key = key;
169     queueItem->tag = tag;
170
171     FIXME("(%x %d %d): stub???\n", hPQ, tag, key);
172     return TRUE;
173 }
174
175 /**********************************************************************
176  *           MinPQ   (GDI.231)
177  *
178  */
179 INT16 WINAPI MinPQ16(HPQ16 hPQ)
180 {
181     FIXME("(%x): stub\n", hPQ);
182     return 0;
183 }
184
185 /**********************************************************************
186  *           SizePQ   (GDI.234)
187  *
188  */
189 INT16 WINAPI SizePQ16(HPQ16 hPQ, INT16 sizechange)
190 {
191     FIXME("(%x %d): stub\n", hPQ, sizechange);
192     return -1;
193 }
194
195
196
197 /*
198  * The following functions implement part of the spooling process to
199  * print manager.  I would like to see wine have a version of print managers
200  * that used LPR/LPD.  For simplicity print jobs will be sent to a file for
201  * now.
202  */
203 typedef struct PRINTJOB
204 {
205     char        *pszOutput;
206     char        *pszTitle;
207     HDC16       hDC;
208     HANDLE16    hHandle;
209     int         nIndex;
210     int         fd;
211     pid_t       pid;
212 } PRINTJOB, *PPRINTJOB;
213
214 #define MAX_PRINT_JOBS 1
215 #define SP_OK 1
216
217 static PPRINTJOB gPrintJobsTable[MAX_PRINT_JOBS];
218
219
220 static PPRINTJOB FindPrintJobFromHandle(HANDLE16 hHandle)
221 {
222     return gPrintJobsTable[0];
223 }
224
225 static int CreateSpoolFile(LPCSTR pszOutput, pid_t *out_pid)
226 {
227     int fd=-1;
228     char psCmd[1024];
229     const char *psCmdP = psCmd;
230     HKEY hkey;
231
232     /* TTD convert the 'output device' into a spool file name */
233
234     if (pszOutput == NULL || *pszOutput == '\0' || out_pid == NULL)
235       return -1;
236
237     *out_pid = -1;
238
239     psCmd[0] = 0;
240     /* @@ Wine registry key: HKCU\Software\Wine\Printing\Spooler */
241     if(!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\Printing\\Spooler", &hkey))
242     {
243         DWORD type, count = sizeof(psCmd);
244         RegQueryValueExA(hkey, pszOutput, 0, &type, (LPBYTE)psCmd, &count);
245         RegCloseKey(hkey);
246     }
247     if (!psCmd[0] && !strncmp("LPR:",pszOutput,4))
248         sprintf(psCmd,"|lpr -P'%s'",pszOutput+4);
249
250     TRACE("Got printerSpoolCommand '%s' for output device '%s'\n",
251           psCmd, pszOutput);
252     if (!*psCmd)
253         psCmdP = pszOutput;
254     else
255     {
256         while (*psCmdP && isspace(*psCmdP))
257         {
258             psCmdP++;
259         }
260         if (!*psCmdP)
261             return -1;
262     }
263     TRACE("command: '%s'\n", psCmdP);
264 #ifdef HAVE_FORK
265     if (*psCmdP == '|')
266     {
267         int fds[2];
268         if (pipe(fds)) {
269             ERR("pipe() failed!\n");
270             return -1;
271         }
272         if ((*out_pid = fork()) == 0)
273         {
274             psCmdP++;
275
276             TRACE("In child need to exec %s\n",psCmdP);
277             close(0);
278             dup2(fds[0],0);
279             close (fds[1]);
280
281             /* reset signals that we previously set to SIG_IGN */
282             signal( SIGPIPE, SIG_DFL );
283
284             execl("/bin/sh", "/bin/sh", "-c", psCmdP, NULL);
285             _exit(1);
286
287         }
288         close (fds[0]);
289         fd = fds[1];
290         TRACE("Need to execute a cmnd and pipe the output to it\n");
291     }
292     else
293 #endif
294     {
295         char *buffer;
296         WCHAR psCmdPW[MAX_PATH];
297
298         TRACE("Just assume it's a file\n");
299
300         /**
301          * The file name can be dos based, we have to find its
302          * corresponding Unix file name.
303          */
304         MultiByteToWideChar(CP_ACP, 0, psCmdP, -1, psCmdPW, MAX_PATH);
305         if ((buffer = wine_get_unix_file_name(psCmdPW)))
306         {
307             if ((fd = open(buffer, O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0)
308             {
309                 ERR("Failed to create spool file '%s' ('%s'). (error %s)\n",
310                     buffer, psCmdP, strerror(errno));
311             }
312             HeapFree(GetProcessHeap(), 0, buffer);
313         }
314     }
315     return fd;
316 }
317
318 static int FreePrintJob(HANDLE16 hJob)
319 {
320     int nRet = SP_ERROR;
321     PPRINTJOB pPrintJob;
322
323     pPrintJob = FindPrintJobFromHandle(hJob);
324     if (pPrintJob != NULL)
325     {
326         nRet = SP_OK;
327         gPrintJobsTable[pPrintJob->nIndex] = NULL;
328         HeapFree(GetProcessHeap(), 0, pPrintJob->pszOutput);
329         HeapFree(GetProcessHeap(), 0, pPrintJob->pszTitle);
330         if (pPrintJob->fd >= 0) close(pPrintJob->fd);
331         if (pPrintJob->pid > 0)
332         {
333             pid_t wret;
334             int status;
335             do {
336                 wret = waitpid(pPrintJob->pid, &status, 0);
337             } while (wret < 0 && errno == EINTR);
338             if (wret < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
339                 nRet = SP_ERROR;
340         }
341         HeapFree(GetProcessHeap(), 0, pPrintJob);
342     }
343     return nRet;
344 }
345
346 /**********************************************************************
347  *           OpenJob   (GDI.240)
348  *
349  */
350 HPJOB16 WINAPI OpenJob16(LPCSTR lpOutput, LPCSTR lpTitle, HDC16 hDC)
351 {
352     HPJOB16 hHandle = (HPJOB16)SP_ERROR;
353     PPRINTJOB pPrintJob;
354
355     TRACE("'%s' '%s' %04x\n", lpOutput, lpTitle, hDC);
356
357     pPrintJob = gPrintJobsTable[0];
358     if (pPrintJob == NULL)
359     {
360         int fd;
361         pid_t pid;
362
363         /* Try and create a spool file */
364         fd = CreateSpoolFile(lpOutput, &pid);
365         if (fd >= 0)
366         {
367             pPrintJob = HeapAlloc(GetProcessHeap(), 0, sizeof(PRINTJOB));
368             if(pPrintJob == NULL) {
369                 WARN("Memory exausted!\n");
370                 return hHandle;
371             }
372
373             hHandle = 1;
374
375             pPrintJob->pszOutput = HeapAlloc(GetProcessHeap(), 0, strlen(lpOutput)+1);
376             strcpy( pPrintJob->pszOutput, lpOutput );
377             if(lpTitle)
378             {
379                 pPrintJob->pszTitle = HeapAlloc(GetProcessHeap(), 0, strlen(lpTitle)+1);
380                 strcpy( pPrintJob->pszTitle, lpTitle );
381             }
382             pPrintJob->hDC = hDC;
383             pPrintJob->fd = fd;
384             pPrintJob->pid = pid;
385             pPrintJob->nIndex = 0;
386             pPrintJob->hHandle = hHandle;
387             gPrintJobsTable[pPrintJob->nIndex] = pPrintJob;
388         }
389     }
390     TRACE("return %04x\n", hHandle);
391     return hHandle;
392 }
393
394 /**********************************************************************
395  *           CloseJob   (GDI.243)
396  *
397  */
398 INT16 WINAPI CloseJob16(HPJOB16 hJob)
399 {
400     int nRet = SP_ERROR;
401     PPRINTJOB pPrintJob = NULL;
402
403     TRACE("%04x\n", hJob);
404
405     pPrintJob = FindPrintJobFromHandle(hJob);
406     if (pPrintJob != NULL)
407     {
408         FreePrintJob(hJob);
409         nRet  = 1;
410     }
411     return nRet;
412 }
413
414 /**********************************************************************
415  *           WriteSpool   (GDI.241)
416  *
417  */
418 INT16 WINAPI WriteSpool16(HPJOB16 hJob, LPSTR lpData, INT16 cch)
419 {
420     int nRet = SP_ERROR;
421     PPRINTJOB pPrintJob = NULL;
422
423     TRACE("%04x %p %04x\n", hJob, lpData, cch);
424
425     pPrintJob = FindPrintJobFromHandle(hJob);
426     if (pPrintJob != NULL && pPrintJob->fd >= 0 && cch)
427     {
428         if (write(pPrintJob->fd, lpData, cch) != cch)
429           nRet = SP_OUTOFDISK;
430         else
431           nRet = cch;
432 #if 0
433         /* FIXME: We just cannot call 16 bit functions from here, since we
434          * have acquired several locks (DC). And we do not really need to.
435          */
436         if (pPrintJob->hDC == 0) {
437             TRACE("hDC == 0 so no QueryAbort\n");
438         }
439         else if (!(QueryAbort16(pPrintJob->hDC, (nRet == SP_OUTOFDISK) ? nRet : 0 )))
440         {
441             CloseJob16(hJob); /* printing aborted */
442             nRet = SP_APPABORT;
443         }
444 #endif
445     }
446     return nRet;
447 }
448
449 typedef INT (WINAPI *MSGBOX_PROC)( HWND, LPCSTR, LPCSTR, UINT );
450
451 /**********************************************************************
452  *           WriteDialog   (GDI.242)
453  *
454  */
455 INT16 WINAPI WriteDialog16(HPJOB16 hJob, LPSTR lpMsg, INT16 cchMsg)
456 {
457     HMODULE mod;
458     MSGBOX_PROC pMessageBoxA;
459     INT16 ret = 0;
460
461     TRACE("%04x %04x '%s'\n", hJob,  cchMsg, lpMsg);
462
463     if ((mod = GetModuleHandleA("user32.dll")))
464     {
465         if ((pMessageBoxA = (MSGBOX_PROC)GetProcAddress( mod, "MessageBoxA" )))
466             ret = pMessageBoxA(0, lpMsg, "Printing Error", MB_OKCANCEL);
467     }
468     return ret;
469 }
470
471
472 /**********************************************************************
473  *           DeleteJob  (GDI.244)
474  *
475  */
476 INT16 WINAPI DeleteJob16(HPJOB16 hJob, INT16 nNotUsed)
477 {
478     int nRet;
479
480     TRACE("%04x\n", hJob);
481
482     nRet = FreePrintJob(hJob);
483     return nRet;
484 }
485
486 /*
487  * The following two function would allow a page to be sent to the printer
488  * when it has been processed.  For simplicity they haven't been implemented.
489  * This means a whole job has to be processed before it is sent to the printer.
490  */
491
492 /**********************************************************************
493  *           StartSpoolPage   (GDI.246)
494  *
495  */
496 INT16 WINAPI StartSpoolPage16(HPJOB16 hJob)
497 {
498     FIXME("StartSpoolPage GDI.246 unimplemented\n");
499     return 1;
500
501 }
502
503
504 /**********************************************************************
505  *           EndSpoolPage   (GDI.247)
506  *
507  */
508 INT16 WINAPI EndSpoolPage16(HPJOB16 hJob)
509 {
510     FIXME("EndSpoolPage GDI.247 unimplemented\n");
511     return 1;
512 }
513
514
515 /**********************************************************************
516  *           GetSpoolJob   (GDI.245)
517  *
518  */
519 DWORD WINAPI GetSpoolJob16(int nOption, LONG param)
520 {
521     DWORD retval = 0;
522     TRACE("In GetSpoolJob param 0x%x noption %d\n",param, nOption);
523     return retval;
524 }
525
526
527 /******************************************************************
528  *                  DrvGetPrinterDataInternal
529  *
530  * Helper for DrvGetPrinterData
531  */
532 static DWORD DrvGetPrinterDataInternal(LPCSTR RegStr_Printer,
533 LPBYTE lpPrinterData, int cbData, int what)
534 {
535     DWORD res = -1;
536     HKEY hkey;
537     DWORD dwType, cbQueryData;
538
539     if (!(RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey))) {
540         if (what == INT_PD_DEFAULT_DEVMODE) { /* "Default DevMode" */
541             if (!(RegQueryValueExA(hkey, DefaultDevMode, 0, &dwType, 0, &cbQueryData))) {
542                 if (!lpPrinterData)
543                     res = cbQueryData;
544                 else if ((cbQueryData) && (cbQueryData <= cbData)) {
545                     cbQueryData = cbData;
546                     if (RegQueryValueExA(hkey, DefaultDevMode, 0,
547                                 &dwType, lpPrinterData, &cbQueryData))
548                         res = cbQueryData;
549                 }
550             }
551         } else { /* "Printer Driver" */
552             cbQueryData = 32;
553             RegQueryValueExA(hkey, "Printer Driver", 0,
554                         &dwType, lpPrinterData, &cbQueryData);
555             res = cbQueryData;
556         }
557     }
558     if (hkey) RegCloseKey(hkey);
559     return res;
560 }
561
562 /******************************************************************
563  *                DrvGetPrinterData     (GDI.282)
564  *
565  */
566 DWORD WINAPI DrvGetPrinterData16(LPSTR lpPrinter, LPSTR lpProfile,
567                                LPDWORD lpType, LPBYTE lpPrinterData,
568                                int cbData, LPDWORD lpNeeded)
569 {
570     LPSTR RegStr_Printer;
571     HKEY hkey = 0, hkey2 = 0;
572     DWORD res = 0;
573     DWORD dwType, PrinterAttr, cbPrinterAttr, SetData, size;
574
575     if (HIWORD(lpPrinter))
576             TRACE("printer %s\n",lpPrinter);
577     else
578             TRACE("printer %p\n",lpPrinter);
579     if (HIWORD(lpProfile))
580             TRACE("profile %s\n",lpProfile);
581     else
582             TRACE("profile %p\n",lpProfile);
583     TRACE("lpType %p\n",lpType);
584
585     if ((!lpPrinter) || (!lpProfile) || (!lpNeeded))
586         return ERROR_INVALID_PARAMETER;
587
588     RegStr_Printer = HeapAlloc(GetProcessHeap(), 0,
589                                strlen(Printers) + strlen(lpPrinter) + 2);
590     strcpy(RegStr_Printer, Printers);
591     strcat(RegStr_Printer, lpPrinter);
592
593     if ((PtrToUlong(lpProfile) == INT_PD_DEFAULT_DEVMODE) || (HIWORD(lpProfile) &&
594     (!strcmp(lpProfile, DefaultDevMode)))) {
595         size = DrvGetPrinterDataInternal(RegStr_Printer, lpPrinterData, cbData,
596                                          INT_PD_DEFAULT_DEVMODE);
597         if (size+1) {
598             *lpNeeded = size;
599             if ((lpPrinterData) && (*lpNeeded > cbData))
600                 res = ERROR_MORE_DATA;
601         }
602         else res = ERROR_INVALID_PRINTER_NAME;
603     }
604     else
605     if ((PtrToUlong(lpProfile) == INT_PD_DEFAULT_MODEL) || (HIWORD(lpProfile) &&
606     (!strcmp(lpProfile, PrinterModel)))) {
607         *lpNeeded = 32;
608         if (!lpPrinterData) goto failed;
609         if (cbData < 32) {
610             res = ERROR_MORE_DATA;
611             goto failed;
612         }
613         size = DrvGetPrinterDataInternal(RegStr_Printer, lpPrinterData, cbData,
614                                          INT_PD_DEFAULT_MODEL);
615         if ((size+1) && (lpType))
616             *lpType = REG_SZ;
617         else
618             res = ERROR_INVALID_PRINTER_NAME;
619     }
620     else
621     {
622         if ((res = RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey)))
623             goto failed;
624         cbPrinterAttr = 4;
625         if ((res = RegQueryValueExA(hkey, "Attributes", 0,
626                         &dwType, (LPBYTE)&PrinterAttr, &cbPrinterAttr)))
627             goto failed;
628         if ((res = RegOpenKeyA(hkey, PrinterDriverData, &hkey2)))
629             goto failed;
630         *lpNeeded = cbData;
631         res = RegQueryValueExA(hkey2, lpProfile, 0,
632                 lpType, lpPrinterData, lpNeeded);
633         if ((res != ERROR_CANTREAD) &&
634          ((PrinterAttr &
635         (PRINTER_ATTRIBUTE_ENABLE_BIDI|PRINTER_ATTRIBUTE_NETWORK))
636         == PRINTER_ATTRIBUTE_NETWORK))
637         {
638             if (!(res) && (*lpType == REG_DWORD) && (*(LPDWORD)lpPrinterData == -1))
639                 res = ERROR_INVALID_DATA;
640         }
641         else
642         {
643             SetData = -1;
644             RegSetValueExA(hkey2, lpProfile, 0, REG_DWORD, (LPBYTE)&SetData, 4); /* no result returned */
645         }
646     }
647
648 failed:
649     if (hkey2) RegCloseKey(hkey2);
650     if (hkey) RegCloseKey(hkey);
651     HeapFree(GetProcessHeap(), 0, RegStr_Printer);
652     return res;
653 }
654
655
656 /******************************************************************
657  *                 DrvSetPrinterData     (GDI.281)
658  *
659  */
660 DWORD WINAPI DrvSetPrinterData16(LPSTR lpPrinter, LPSTR lpProfile,
661                                DWORD lpType, LPBYTE lpPrinterData,
662                                DWORD dwSize)
663 {
664     LPSTR RegStr_Printer;
665     HKEY hkey = 0;
666     DWORD res = 0;
667
668     if (HIWORD(lpPrinter))
669             TRACE("printer %s\n",lpPrinter);
670     else
671             TRACE("printer %p\n",lpPrinter);
672     if (HIWORD(lpProfile))
673             TRACE("profile %s\n",lpProfile);
674     else
675             TRACE("profile %p\n",lpProfile);
676     TRACE("lpType %08x\n",lpType);
677
678     if ((!lpPrinter) || (!lpProfile) ||
679     (PtrToUlong(lpProfile) == INT_PD_DEFAULT_MODEL) || (HIWORD(lpProfile) &&
680     (!strcmp(lpProfile, PrinterModel))))
681         return ERROR_INVALID_PARAMETER;
682
683     RegStr_Printer = HeapAlloc(GetProcessHeap(), 0,
684                         strlen(Printers) + strlen(lpPrinter) + 2);
685     strcpy(RegStr_Printer, Printers);
686     strcat(RegStr_Printer, lpPrinter);
687
688     if ((PtrToUlong(lpProfile) == INT_PD_DEFAULT_DEVMODE) || (HIWORD(lpProfile) &&
689     (!strcmp(lpProfile, DefaultDevMode)))) {
690         if ( RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey)
691              != ERROR_SUCCESS ||
692              RegSetValueExA(hkey, DefaultDevMode, 0, REG_BINARY,
693                               lpPrinterData, dwSize) != ERROR_SUCCESS )
694                 res = ERROR_INVALID_PRINTER_NAME;
695     }
696     else
697     {
698         strcat(RegStr_Printer, "\\");
699
700         if( (res = RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey)) ==
701             ERROR_SUCCESS ) {
702
703             if (!lpPrinterData)
704                 res = RegDeleteValueA(hkey, lpProfile);
705             else
706                 res = RegSetValueExA(hkey, lpProfile, 0, lpType,
707                                        lpPrinterData, dwSize);
708         }
709     }
710
711     if (hkey) RegCloseKey(hkey);
712     HeapFree(GetProcessHeap(), 0, RegStr_Printer);
713     return res;
714 }