/* * Implementation of some printer driver bits * * Copyright 1996 John Harvey * Copyright 1998 Huw Davies * Copyright 1998 Andreas Mohr * Copyright 1999 Klaas van Gend * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "wine/port.h" #include #include #include #include #include #include #include #ifdef HAVE_IO_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #include "windef.h" #include "winbase.h" #include "winuser.h" #include "wine/winbase16.h" #include "wine/wingdi16.h" #include "winspool.h" #include "winerror.h" #include "winreg.h" #include "wownt32.h" #include "wine/debug.h" #include "gdi.h" #include "gdi_private.h" #include "heap.h" WINE_DEFAULT_DEBUG_CHANNEL(print); static const char PrinterModel[] = "Printer Model"; static const char DefaultDevMode[] = "Default DevMode"; static const char PrinterDriverData[] = "PrinterDriverData"; static const char Printers[] = "System\\CurrentControlSet\\Control\\Print\\Printers\\"; /****************************************************************** * StartDocA [GDI32.@] * * StartDoc calls the STARTDOC Escape with the input data pointing to DocName * and the output data (which is used as a second input parameter).pointing at * the whole docinfo structure. This seems to be an undocumented feature of * the STARTDOC Escape. * * Note: we now do it the other way, with the STARTDOC Escape calling StartDoc. */ INT WINAPI StartDocA(HDC hdc, const DOCINFOA* doc) { INT ret = 0; DC *dc = DC_GetDCPtr( hdc ); TRACE("DocName = '%s' Output = '%s' Datatype = '%s'\n", doc->lpszDocName, doc->lpszOutput, doc->lpszDatatype); if(!dc) return SP_ERROR; if (dc->funcs->pStartDoc) ret = dc->funcs->pStartDoc( dc->physDev, doc ); GDI_ReleaseObj( hdc ); return ret; } /************************************************************************* * StartDocW [GDI32.@] * */ INT WINAPI StartDocW(HDC hdc, const DOCINFOW* doc) { DOCINFOA docA; INT ret; docA.cbSize = doc->cbSize; docA.lpszDocName = doc->lpszDocName ? HEAP_strdupWtoA( GetProcessHeap(), 0, doc->lpszDocName ) : NULL; docA.lpszOutput = doc->lpszOutput ? HEAP_strdupWtoA( GetProcessHeap(), 0, doc->lpszOutput ) : NULL; docA.lpszDatatype = doc->lpszDatatype ? HEAP_strdupWtoA( GetProcessHeap(), 0, doc->lpszDatatype ) : NULL; docA.fwType = doc->fwType; ret = StartDocA(hdc, &docA); HeapFree( GetProcessHeap(), 0, (LPSTR)docA.lpszDocName ); HeapFree( GetProcessHeap(), 0, (LPSTR)docA.lpszOutput ); HeapFree( GetProcessHeap(), 0, (LPSTR)docA.lpszDatatype ); return ret; } /****************************************************************** * EndDoc [GDI32.@] * */ INT WINAPI EndDoc(HDC hdc) { INT ret = 0; DC *dc = DC_GetDCPtr( hdc ); if(!dc) return SP_ERROR; if (dc->funcs->pEndDoc) ret = dc->funcs->pEndDoc( dc->physDev ); GDI_ReleaseObj( hdc ); return ret; } /****************************************************************** * StartPage [GDI32.@] * */ INT WINAPI StartPage(HDC hdc) { INT ret = 1; DC *dc = DC_GetDCPtr( hdc ); if(!dc) return SP_ERROR; if(dc->funcs->pStartPage) ret = dc->funcs->pStartPage( dc->physDev ); else FIXME("stub\n"); GDI_ReleaseObj( hdc ); return ret; } /****************************************************************** * EndPage [GDI32.@] * */ INT WINAPI EndPage(HDC hdc) { ABORTPROC abort_proc; INT ret = 0; DC *dc = DC_GetDCPtr( hdc ); if(!dc) return SP_ERROR; if (dc->funcs->pEndPage) ret = dc->funcs->pEndPage( dc->physDev ); abort_proc = dc->pAbortProc; GDI_ReleaseObj( hdc ); if (abort_proc && !abort_proc( hdc, 0 )) { EndDoc( hdc ); ret = 0; } return ret; } /****************************************************************************** * AbortDoc [GDI32.@] */ INT WINAPI AbortDoc(HDC hdc) { INT ret = 0; DC *dc = DC_GetDCPtr( hdc ); if(!dc) return SP_ERROR; if (dc->funcs->pAbortDoc) ret = dc->funcs->pAbortDoc( dc->physDev ); GDI_ReleaseObj( hdc ); return ret; } /********************************************************************** * QueryAbort (GDI.155) * * Calls the app's AbortProc function if avail. * * RETURNS * TRUE if no AbortProc avail or AbortProc wants to continue printing. * FALSE if AbortProc wants to abort printing. */ BOOL16 WINAPI QueryAbort16(HDC16 hdc16, INT16 reserved) { BOOL ret = TRUE; HDC hdc = HDC_32( hdc16 ); DC *dc = DC_GetDCPtr( hdc ); ABORTPROC abproc; if(!dc) { ERR("Invalid hdc %p\n", hdc); return FALSE; } abproc = dc->pAbortProc; GDI_ReleaseObj( hdc ); if (abproc) ret = abproc(hdc, 0); return ret; } /********************************************************************** * call_abort_proc16 */ static BOOL CALLBACK call_abort_proc16( HDC hdc, INT code ) { ABORTPROC16 proc16; DC *dc = DC_GetDCPtr( hdc ); if (!dc) return FALSE; proc16 = dc->pAbortProc16; GDI_ReleaseObj( hdc ); if (proc16) { WORD args[2]; DWORD ret; args[1] = HDC_16(hdc); args[0] = code; WOWCallback16Ex( (DWORD)proc16, WCB16_PASCAL, sizeof(args), args, &ret ); return LOWORD(ret); } return TRUE; } /********************************************************************** * SetAbortProc (GDI.381) */ INT16 WINAPI SetAbortProc16(HDC16 hdc16, ABORTPROC16 abrtprc) { HDC hdc = HDC_32( hdc16 ); DC *dc = DC_GetDCPtr( hdc ); if (!dc) return FALSE; dc->pAbortProc16 = abrtprc; GDI_ReleaseObj( hdc ); return SetAbortProc( hdc, call_abort_proc16 ); } /********************************************************************** * SetAbortProc (GDI32.@) * */ INT WINAPI SetAbortProc(HDC hdc, ABORTPROC abrtprc) { DC *dc = DC_GetDCPtr( hdc ); if (!dc) return FALSE; dc->pAbortProc = abrtprc; GDI_ReleaseObj( hdc ); return TRUE; } /****************** misc. printer related functions */ /* * The following function should implement a queing system */ struct hpq { struct hpq *next; int tag; int key; }; static struct hpq *hpqueue; /********************************************************************** * CreatePQ (GDI.230) * */ HPQ16 WINAPI CreatePQ16(INT16 size) { #if 0 HGLOBAL16 hpq = 0; WORD tmp_size; LPWORD pPQ; tmp_size = size << 2; if (!(hpq = GlobalAlloc16(GMEM_SHARE|GMEM_MOVEABLE, tmp_size + 8))) return 0xffff; pPQ = GlobalLock16(hpq); *pPQ++ = 0; *pPQ++ = tmp_size; *pPQ++ = 0; *pPQ++ = 0; GlobalUnlock16(hpq); return (HPQ16)hpq; #else FIXME("(%d): stub\n",size); return 1; #endif } /********************************************************************** * DeletePQ (GDI.235) * */ INT16 WINAPI DeletePQ16(HPQ16 hPQ) { return GlobalFree16((HGLOBAL16)hPQ); } /********************************************************************** * ExtractPQ (GDI.232) * */ INT16 WINAPI ExtractPQ16(HPQ16 hPQ) { struct hpq *queue, *prev, *current, *currentPrev; int key = 0, tag = -1; currentPrev = prev = NULL; queue = current = hpqueue; if (current) key = current->key; while (current) { currentPrev = current; current = current->next; if (current) { if (current->key < key) { queue = current; prev = currentPrev; } } } if (queue) { tag = queue->tag; if (prev) prev->next = queue->next; else hpqueue = queue->next; HeapFree(GetProcessHeap(), 0, queue); } TRACE("%x got tag %d key %d\n", hPQ, tag, key); return tag; } /********************************************************************** * InsertPQ (GDI.233) * */ INT16 WINAPI InsertPQ16(HPQ16 hPQ, INT16 tag, INT16 key) { struct hpq *queueItem = HeapAlloc(GetProcessHeap(), 0, sizeof(struct hpq)); if(queueItem == NULL) { ERR("Memory exausted!\n"); return FALSE; } queueItem->next = hpqueue; hpqueue = queueItem; queueItem->key = key; queueItem->tag = tag; FIXME("(%x %d %d): stub???\n", hPQ, tag, key); return TRUE; } /********************************************************************** * MinPQ (GDI.231) * */ INT16 WINAPI MinPQ16(HPQ16 hPQ) { FIXME("(%x): stub\n", hPQ); return 0; } /********************************************************************** * SizePQ (GDI.234) * */ INT16 WINAPI SizePQ16(HPQ16 hPQ, INT16 sizechange) { FIXME("(%x %d): stub\n", hPQ, sizechange); return -1; } /* * The following functions implement part of the spooling process to * print manager. I would like to see wine have a version of print managers * that used LPR/LPD. For simplicity print jobs will be sent to a file for * now. */ typedef struct PRINTJOB { char *pszOutput; char *pszTitle; HDC16 hDC; HANDLE16 hHandle; int nIndex; int fd; } PRINTJOB, *PPRINTJOB; #define MAX_PRINT_JOBS 1 #define SP_OK 1 PPRINTJOB gPrintJobsTable[MAX_PRINT_JOBS]; static PPRINTJOB FindPrintJobFromHandle(HANDLE16 hHandle) { return gPrintJobsTable[0]; } static int CreateSpoolFile(LPCSTR pszOutput) { int fd=-1; char psCmd[1024]; char *psCmdP = psCmd; HKEY hkey; /* TTD convert the 'output device' into a spool file name */ if (pszOutput == NULL || *pszOutput == '\0') return -1; psCmd[0] = 0; if (!strncmp("LPR:",pszOutput,4)) { if(!RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\spooler", &hkey)) { DWORD type, count = sizeof(psCmd); RegQueryValueExA(hkey, pszOutput, 0, &type, psCmd, &count); RegCloseKey(hkey); } if(psCmd[0] == 0) sprintf(psCmd,"|lpr -P%s",pszOutput+4); } else { if(!RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\spooler", &hkey)) { DWORD type, count = sizeof(psCmd); RegQueryValueExA(hkey, pszOutput, 0, &type, psCmd, &count); RegCloseKey(hkey); } } TRACE("Got printerSpoolCommand '%s' for output device '%s'\n", psCmd, pszOutput); if (!*psCmd) psCmdP = (char *)pszOutput; else { while (*psCmdP && isspace(*psCmdP)) { psCmdP++; } if (!*psCmdP) return -1; } TRACE("command: '%s'\n", psCmdP); #ifdef HAVE_FORK if (*psCmdP == '|') { int fds[2]; if (pipe(fds)) { ERR("pipe() failed!\n"); return -1; } if (fork() == 0) { psCmdP++; TRACE("In child need to exec %s\n",psCmdP); close(0); dup2(fds[0],0); close (fds[1]); /* reset signals that we previously set to SIG_IGN */ signal( SIGPIPE, SIG_DFL ); signal( SIGCHLD, SIG_DFL ); system(psCmdP); exit(0); } close (fds[0]); fd = fds[1]; TRACE("Need to execute a cmnd and pipe the output to it\n"); } else #endif { char *buffer; WCHAR psCmdPW[MAX_PATH]; TRACE("Just assume it's a file\n"); /** * The file name can be dos based, we have to find its * Unix correspondant file name */ MultiByteToWideChar(CP_ACP, 0, psCmdP, -1, psCmdPW, MAX_PATH); if ((buffer = wine_get_unix_file_name(psCmdPW))) { if ((fd = open(buffer, O_CREAT | O_TRUNC | O_WRONLY , 0600)) < 0) { ERR("Failed to create spool file '%s' ('%s'). (error %s)\n", buffer, psCmdP, strerror(errno)); } HeapFree(GetProcessHeap(), 0, buffer); } } return fd; } static int FreePrintJob(HANDLE16 hJob) { int nRet = SP_ERROR; PPRINTJOB pPrintJob; pPrintJob = FindPrintJobFromHandle(hJob); if (pPrintJob != NULL) { gPrintJobsTable[pPrintJob->nIndex] = NULL; HeapFree(GetProcessHeap(), 0, pPrintJob->pszOutput); HeapFree(GetProcessHeap(), 0, pPrintJob->pszTitle); if (pPrintJob->fd >= 0) close(pPrintJob->fd); HeapFree(GetProcessHeap(), 0, pPrintJob); nRet = SP_OK; } return nRet; } /********************************************************************** * OpenJob (GDI.240) * */ HPJOB16 WINAPI OpenJob16(LPCSTR lpOutput, LPCSTR lpTitle, HDC16 hDC) { HPJOB16 hHandle = (HPJOB16)SP_ERROR; PPRINTJOB pPrintJob; TRACE("'%s' '%s' %04x\n", lpOutput, lpTitle, hDC); pPrintJob = gPrintJobsTable[0]; if (pPrintJob == NULL) { int fd; /* Try and create a spool file */ fd = CreateSpoolFile(lpOutput); if (fd >= 0) { pPrintJob = HeapAlloc(GetProcessHeap(), 0, sizeof(PRINTJOB)); if(pPrintJob == NULL) { WARN("Memory exausted!\n"); return hHandle; } hHandle = 1; pPrintJob->pszOutput = HeapAlloc(GetProcessHeap(), 0, strlen(lpOutput)+1); strcpy( pPrintJob->pszOutput, lpOutput ); if(lpTitle) { pPrintJob->pszTitle = HeapAlloc(GetProcessHeap(), 0, strlen(lpTitle)+1); strcpy( pPrintJob->pszTitle, lpTitle ); } pPrintJob->hDC = hDC; pPrintJob->fd = fd; pPrintJob->nIndex = 0; pPrintJob->hHandle = hHandle; gPrintJobsTable[pPrintJob->nIndex] = pPrintJob; } } TRACE("return %04x\n", hHandle); return hHandle; } /********************************************************************** * CloseJob (GDI.243) * */ INT16 WINAPI CloseJob16(HPJOB16 hJob) { int nRet = SP_ERROR; PPRINTJOB pPrintJob = NULL; TRACE("%04x\n", hJob); pPrintJob = FindPrintJobFromHandle(hJob); if (pPrintJob != NULL) { /* Close the spool file */ close(pPrintJob->fd); FreePrintJob(hJob); nRet = 1; } return nRet; } /********************************************************************** * WriteSpool (GDI.241) * */ INT16 WINAPI WriteSpool16(HPJOB16 hJob, LPSTR lpData, INT16 cch) { int nRet = SP_ERROR; PPRINTJOB pPrintJob = NULL; TRACE("%04x %08lx %04x\n", hJob, (DWORD)lpData, cch); pPrintJob = FindPrintJobFromHandle(hJob); if (pPrintJob != NULL && pPrintJob->fd >= 0 && cch) { if (write(pPrintJob->fd, lpData, cch) != cch) nRet = SP_OUTOFDISK; else nRet = cch; #if 0 /* FIXME: We just cannot call 16 bit functions from here, since we * have acquired several locks (DC). And we do not really need to. */ if (pPrintJob->hDC == 0) { TRACE("hDC == 0 so no QueryAbort\n"); } else if (!(QueryAbort16(pPrintJob->hDC, (nRet == SP_OUTOFDISK) ? nRet : 0 ))) { CloseJob16(hJob); /* printing aborted */ nRet = SP_APPABORT; } #endif } return nRet; } typedef INT (WINAPI *MSGBOX_PROC)( HWND, LPCSTR, LPCSTR, UINT ); /********************************************************************** * WriteDialog (GDI.242) * */ INT16 WINAPI WriteDialog16(HPJOB16 hJob, LPSTR lpMsg, INT16 cchMsg) { HMODULE mod; MSGBOX_PROC pMessageBoxA; INT16 ret = 0; TRACE("%04x %04x '%s'\n", hJob, cchMsg, lpMsg); if ((mod = GetModuleHandleA("user32.dll"))) { if ((pMessageBoxA = (MSGBOX_PROC)GetProcAddress( mod, "MessageBoxA" ))) ret = pMessageBoxA(0, lpMsg, "Printing Error", MB_OKCANCEL); } return ret; } /********************************************************************** * DeleteJob (GDI.244) * */ INT16 WINAPI DeleteJob16(HPJOB16 hJob, INT16 nNotUsed) { int nRet; TRACE("%04x\n", hJob); nRet = FreePrintJob(hJob); return nRet; } /* * The following two function would allow a page to be sent to the printer * when it has been processed. For simplicity they havn't been implemented. * This means a whole job has to be processed before it is sent to the printer. */ /********************************************************************** * StartSpoolPage (GDI.246) * */ INT16 WINAPI StartSpoolPage16(HPJOB16 hJob) { FIXME("StartSpoolPage GDI.246 unimplemented\n"); return 1; } /********************************************************************** * EndSpoolPage (GDI.247) * */ INT16 WINAPI EndSpoolPage16(HPJOB16 hJob) { FIXME("EndSpoolPage GDI.247 unimplemented\n"); return 1; } /********************************************************************** * GetSpoolJob (GDI.245) * */ DWORD WINAPI GetSpoolJob16(int nOption, LONG param) { DWORD retval = 0; TRACE("In GetSpoolJob param 0x%lx noption %d\n",param, nOption); return retval; } /****************************************************************** * DrvGetPrinterDataInternal * * Helper for DrvGetPrinterData */ static DWORD DrvGetPrinterDataInternal(LPSTR RegStr_Printer, LPBYTE lpPrinterData, int cbData, int what) { DWORD res = -1; HKEY hkey; DWORD dwType, cbQueryData; if (!(RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey))) { if (what == INT_PD_DEFAULT_DEVMODE) { /* "Default DevMode" */ if (!(RegQueryValueExA(hkey, DefaultDevMode, 0, &dwType, 0, &cbQueryData))) { if (!lpPrinterData) res = cbQueryData; else if ((cbQueryData) && (cbQueryData <= cbData)) { cbQueryData = cbData; if (RegQueryValueExA(hkey, DefaultDevMode, 0, &dwType, lpPrinterData, &cbQueryData)) res = cbQueryData; } } } else { /* "Printer Driver" */ cbQueryData = 32; RegQueryValueExA(hkey, "Printer Driver", 0, &dwType, lpPrinterData, &cbQueryData); res = cbQueryData; } } if (hkey) RegCloseKey(hkey); return res; } /****************************************************************** * DrvGetPrinterData (GDI.282) * */ DWORD WINAPI DrvGetPrinterData16(LPSTR lpPrinter, LPSTR lpProfile, LPDWORD lpType, LPBYTE lpPrinterData, int cbData, LPDWORD lpNeeded) { LPSTR RegStr_Printer; HKEY hkey = 0, hkey2 = 0; DWORD res = 0; DWORD dwType, PrinterAttr, cbPrinterAttr, SetData, size; if (HIWORD(lpPrinter)) TRACE("printer %s\n",lpPrinter); else TRACE("printer %p\n",lpPrinter); if (HIWORD(lpProfile)) TRACE("profile %s\n",lpProfile); else TRACE("profile %p\n",lpProfile); TRACE("lpType %p\n",lpType); if ((!lpPrinter) || (!lpProfile) || (!lpNeeded)) return ERROR_INVALID_PARAMETER; RegStr_Printer = HeapAlloc(GetProcessHeap(), 0, strlen(Printers) + strlen(lpPrinter) + 2); strcpy(RegStr_Printer, Printers); strcat(RegStr_Printer, lpPrinter); if (((DWORD)lpProfile == INT_PD_DEFAULT_DEVMODE) || (HIWORD(lpProfile) && (!strcmp(lpProfile, DefaultDevMode)))) { size = DrvGetPrinterDataInternal(RegStr_Printer, lpPrinterData, cbData, INT_PD_DEFAULT_DEVMODE); if (size+1) { *lpNeeded = size; if ((lpPrinterData) && (*lpNeeded > cbData)) res = ERROR_MORE_DATA; } else res = ERROR_INVALID_PRINTER_NAME; } else if (((DWORD)lpProfile == INT_PD_DEFAULT_MODEL) || (HIWORD(lpProfile) && (!strcmp(lpProfile, PrinterModel)))) { *lpNeeded = 32; if (!lpPrinterData) goto failed; if (cbData < 32) { res = ERROR_MORE_DATA; goto failed; } size = DrvGetPrinterDataInternal(RegStr_Printer, lpPrinterData, cbData, INT_PD_DEFAULT_MODEL); if ((size+1) && (lpType)) *lpType = REG_SZ; else res = ERROR_INVALID_PRINTER_NAME; } else { if ((res = RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey))) goto failed; cbPrinterAttr = 4; if ((res = RegQueryValueExA(hkey, "Attributes", 0, &dwType, (LPBYTE)&PrinterAttr, &cbPrinterAttr))) goto failed; if ((res = RegOpenKeyA(hkey, PrinterDriverData, &hkey2))) goto failed; *lpNeeded = cbData; res = RegQueryValueExA(hkey2, lpProfile, 0, lpType, lpPrinterData, lpNeeded); if ((res != ERROR_CANTREAD) && ((PrinterAttr & (PRINTER_ATTRIBUTE_ENABLE_BIDI|PRINTER_ATTRIBUTE_NETWORK)) == PRINTER_ATTRIBUTE_NETWORK)) { if (!(res) && (*lpType == REG_DWORD) && (*(LPDWORD)lpPrinterData == -1)) res = ERROR_INVALID_DATA; } else { SetData = -1; RegSetValueExA(hkey2, lpProfile, 0, REG_DWORD, (LPBYTE)&SetData, 4); /* no result returned */ } } failed: if (hkey2) RegCloseKey(hkey2); if (hkey) RegCloseKey(hkey); HeapFree(GetProcessHeap(), 0, RegStr_Printer); return res; } /****************************************************************** * DrvSetPrinterData (GDI.281) * */ DWORD WINAPI DrvSetPrinterData16(LPSTR lpPrinter, LPSTR lpProfile, DWORD lpType, LPBYTE lpPrinterData, DWORD dwSize) { LPSTR RegStr_Printer; HKEY hkey = 0; DWORD res = 0; if (HIWORD(lpPrinter)) TRACE("printer %s\n",lpPrinter); else TRACE("printer %p\n",lpPrinter); if (HIWORD(lpProfile)) TRACE("profile %s\n",lpProfile); else TRACE("profile %p\n",lpProfile); TRACE("lpType %08lx\n",lpType); if ((!lpPrinter) || (!lpProfile) || ((DWORD)lpProfile == INT_PD_DEFAULT_MODEL) || (HIWORD(lpProfile) && (!strcmp(lpProfile, PrinterModel)))) return ERROR_INVALID_PARAMETER; RegStr_Printer = HeapAlloc(GetProcessHeap(), 0, strlen(Printers) + strlen(lpPrinter) + 2); strcpy(RegStr_Printer, Printers); strcat(RegStr_Printer, lpPrinter); if (((DWORD)lpProfile == INT_PD_DEFAULT_DEVMODE) || (HIWORD(lpProfile) && (!strcmp(lpProfile, DefaultDevMode)))) { if ( RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey) != ERROR_SUCCESS || RegSetValueExA(hkey, DefaultDevMode, 0, REG_BINARY, lpPrinterData, dwSize) != ERROR_SUCCESS ) res = ERROR_INVALID_PRINTER_NAME; } else { strcat(RegStr_Printer, "\\"); if( (res = RegOpenKeyA(HKEY_LOCAL_MACHINE, RegStr_Printer, &hkey)) == ERROR_SUCCESS ) { if (!lpPrinterData) res = RegDeleteValueA(hkey, lpProfile); else res = RegSetValueExA(hkey, lpProfile, 0, lpType, lpPrinterData, dwSize); } } if (hkey) RegCloseKey(hkey); HeapFree(GetProcessHeap(), 0, RegStr_Printer); return res; }