1/*
2 * Copyright 2001-2004 Red Hat Inc., Durham, North Carolina.
3 *
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation on the rights to use, copy, modify, merge,
10 * publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so,
12 * subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial
16 * portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
22 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 * SOFTWARE.
26 */
27
28/*
29 * Authors:
30 *   Kevin E. Martin <kem@redhat.com>
31 *
32 */
33
34/** \file
35 * This file provides support for fonts. */
36
37#ifdef HAVE_DMX_CONFIG_H
38#include <dmx-config.h>
39#endif
40
41#define DMX_FONTPATH_DEBUG 0
42
43#include "dmx.h"
44#include "dmxsync.h"
45#include "dmxfont.h"
46#include "dmxlog.h"
47
48#include <X11/fonts/fontstruct.h>
49#include "dixfont.h"
50#include "dixstruct.h"
51
52static int (*dmxSaveProcVector[256])(ClientPtr);
53static int   dmxFontLastError;
54
55static int dmxFontErrorHandler(Display *dpy, XErrorEvent *ev)
56{
57    dmxFontLastError = ev->error_code;
58
59    return 0;
60}
61
62static char **dmxGetFontPath(int *npaths)
63{
64    char          **fp;
65    unsigned char  *c, *paths;
66    char           *newfp;
67    int             len, l, i;
68
69    GetFontPath(serverClient, npaths, &len, &paths);
70
71    newfp = malloc(*npaths + len);
72    c = (unsigned char *)newfp;
73    fp = malloc(*npaths * sizeof(*fp));
74
75    memmove(newfp, paths+1, *npaths + len - 1);
76    l = *paths;
77    for (i = 0; i < *npaths; i++) {
78	fp[i] = (char *)c;
79	c += l;
80	l = *c;
81	*c++ = '\0';
82    }
83
84#if DMX_FONTPATH_DEBUG
85    for (i = 0; i < *npaths; i++)
86        dmxLog(dmxDebug, "FontPath[%d] = %s\n", i, fp[i]);
87#endif
88
89    return fp;
90}
91
92static void dmxFreeFontPath(char **fp)
93{
94    free(fp[0]);
95    free(fp);
96}
97
98static Bool dmxCheckFontPathElement(DMXScreenInfo *dmxScreen, char *fp)
99{
100    int  (*oldErrorHandler)(Display *, XErrorEvent *);
101
102    if (!dmxScreen->beDisplay)
103	return TRUE;
104
105    dmxFontLastError = 0;
106    oldErrorHandler = XSetErrorHandler(dmxFontErrorHandler);
107    XSetFontPath(dmxScreen->beDisplay, &fp, 1);
108    dmxSync(dmxScreen, TRUE);   /* Must complete before removing handler */
109    XSetErrorHandler(oldErrorHandler);
110
111    return dmxFontLastError == 0;
112}
113
114static int dmxSetFontPath(DMXScreenInfo *dmxScreen)
115{
116    int  (*oldErrorHandler)(Display *, XErrorEvent *);
117    char **fp;
118    int    result = Success;
119    int    npaths;
120
121    if (!dmxScreen->beDisplay)
122	return result;
123
124    fp = dmxGetFontPath(&npaths);
125    if (!fp) return BadAlloc;
126
127    dmxFontLastError = 0;
128    oldErrorHandler = XSetErrorHandler(dmxFontErrorHandler);
129    XSetFontPath(dmxScreen->beDisplay, fp, npaths);
130    dmxSync(dmxScreen, TRUE);   /* Must complete before removing handler */
131    XSetErrorHandler(oldErrorHandler);
132
133    if (dmxFontLastError) {
134	result = dmxFontLastError;
135	/* We could set *error here to the offending path, but it is
136	 * ignored, so we don't bother figuring out which path is bad.
137	 * If we do add this support in the future, we'll need to add
138	 * error to the function's argument list.
139	 */
140    }
141
142    dmxFreeFontPath(fp);
143
144    return result;
145}
146
147static int dmxCheckFontPath(DMXScreenInfo *dmxScreen, int *error)
148{
149    char **oldFontPath;
150    int    nOldPaths;
151    int    result = Success;
152
153    if (!dmxScreen->beDisplay)
154	return result;
155
156    /* Save old font path */
157    oldFontPath = XGetFontPath(dmxScreen->beDisplay, &nOldPaths);
158
159    result = dmxSetFontPath(dmxScreen);
160
161    /* Restore old font path */
162    XSetFontPath(dmxScreen->beDisplay, oldFontPath, nOldPaths);
163    XFreeFontPath(oldFontPath);
164    dmxSync(dmxScreen, FALSE);
165
166    return result;
167}
168
169static int dmxProcSetFontPath(ClientPtr client)
170{
171    unsigned char *ptr;
172    unsigned long  nbytes, total, n;
173    long           nfonts;
174    int            i, result;
175    unsigned char *oldFontPath, *tmpFontPath;
176    int            nOldPaths;
177    int            lenOldPaths;
178    REQUEST(xSetFontPathReq);
179
180    REQUEST_AT_LEAST_SIZE(xSetFontPathReq);
181
182    nbytes = (client->req_len << 2) - sizeof(xSetFontPathReq);
183    total = nbytes;
184    ptr = (unsigned char *)&stuff[1];
185    nfonts = stuff->nFonts;
186
187    while (--nfonts >= 0) {
188	if ((total == 0) || (total < (n = (*ptr + 1))))
189	    return BadLength;
190	total -= n;
191	ptr += n;
192    }
193    if (total >= 4)
194        return BadLength;
195
196    GetFontPath(serverClient, &nOldPaths, &lenOldPaths, &tmpFontPath);
197    oldFontPath = malloc(nOldPaths + lenOldPaths);
198    memmove(oldFontPath, tmpFontPath, nOldPaths + lenOldPaths);
199
200    result = SetFontPath(client, stuff->nFonts, (unsigned char *)&stuff[1]);
201    if (!result) {
202	int error = 0;
203	for (i = 0; i < dmxNumScreens; i++)
204	    if ((result = dmxCheckFontPath(&dmxScreens[i], &error)))
205		break;
206
207	if (result) {
208	    /* Restore old fontpath in the DMX server */
209	    SetFontPath(client, nOldPaths, oldFontPath);
210	    client->errorValue = error;
211	}
212    }
213
214    free(oldFontPath);
215    return result;
216}
217
218/** Initialize font support.  In addition to the screen function call
219 *  pointers, DMX also hooks in at the ProcVector[] level.  Here the old
220 *  ProcVector function pointers are saved and the new ProcVector
221 *  function pointers are initialized. */
222void dmxInitFonts(void)
223{
224    int  i;
225
226    for (i = 0; i < 256; i++)
227	dmxSaveProcVector[i] = ProcVector[i];
228
229    ProcVector[X_SetFontPath] = dmxProcSetFontPath;
230}
231
232/** Reset font support by restoring the original ProcVector function
233 *  pointers. */
234void dmxResetFonts(void)
235{
236    int  i;
237
238    for (i = 0; i < 256; i++)
239	ProcVector[i] = dmxSaveProcVector[i];
240}
241
242/** Load the font, \a pFont, on the back-end server associated with \a
243 *  pScreen.  When a font is loaded, the font path on back-end server is
244 *  first initialized to that specified on the command line with the
245 *  -fontpath options, and then the font is loaded. */
246Bool dmxBELoadFont(ScreenPtr pScreen, FontPtr pFont)
247{
248    DMXScreenInfo  *dmxScreen = &dmxScreens[pScreen->myNum];
249    dmxFontPrivPtr  pFontPriv = FontGetPrivate(pFont, dmxFontPrivateIndex);
250    const char     *name;
251    char          **oldFontPath = NULL;
252    int             nOldPaths;
253    Atom            name_atom, value_atom;
254    int             i;
255
256    /* Make sure we have a font private struct to work with */
257    if (!pFontPriv)
258	return FALSE;
259
260    /* Don't load a font over top of itself */
261    if (pFontPriv->font[pScreen->myNum]) {
262	return TRUE; /* Already loaded font */
263    }
264
265    /* Save old font path */
266    oldFontPath = XGetFontPath(dmxScreen->beDisplay, &nOldPaths);
267
268    /* Set the font path for the font about to be loaded on the back-end */
269    if (dmxSetFontPath(dmxScreen)) {
270	char **fp;
271	int    npaths;
272	Bool  *goodfps;
273
274	/* This could fail only when first starting the X server and
275	 * loading the default font.  If it fails here, then the default
276	 * font path is invalid, no default font path will be set, the
277	 * DMX server will fail to load the default font, and it will
278	 * exit with an error unless we remove the offending font paths
279	 * with the -ignorebadfontpaths command line option.
280	 */
281
282	fp = dmxGetFontPath(&npaths);
283	if (!fp) {
284	    dmxLog(dmxError,
285		   "No default font path set.\n");
286	    dmxLog(dmxError,
287		   "Please see the Xdmx man page for information on how to\n");
288	    dmxLog(dmxError,
289		   "initialize the DMX server's default font path.\n");
290	    XFreeFontPath(oldFontPath);
291	    return FALSE;
292	}
293
294	if (!dmxFontPath)
295	    dmxLog(dmxWarning, "No default font path is set.\n");
296
297	goodfps = malloc(npaths * sizeof(*goodfps));
298
299	dmxLog(dmxError,
300	       "The DMX server failed to set the following font paths on "
301	       "screen #%d:\n", pScreen->myNum);
302
303	for (i = 0; i < npaths; i++)
304	    if (!(goodfps[i] = dmxCheckFontPathElement(dmxScreen, fp[i])))
305		dmxLog(dmxError, "    %s\n", fp[i]);
306
307	if (dmxIgnoreBadFontPaths) {
308	    char *newfp;
309	    int   newnpaths = 0;
310	    int   len = 0;
311	    int   j = 0;
312
313	    dmxLog(dmxError,
314		   "These font paths will not be used because the "
315		   "\"-ignorebadfontpaths\"\n");
316	    dmxLog(dmxError,
317		   "option is set.\n");
318
319	    for (i = 0; i < npaths; i++)
320		if (goodfps[i]) {
321		    len += strlen(fp[i]) + 1;
322		    newnpaths++;
323		}
324
325	    if (!newnpaths) {
326		/* No valid font paths were found */
327		dmxLog(dmxError,
328		       "After removing the font paths above, no valid font "
329		       "paths were\n");
330		dmxLog(dmxError,
331		       "available.  Please check that the font paths set on "
332		       "the command\n");
333		dmxLog(dmxError,
334		       "line or in the configuration file via the "
335		       "\"-fontpath\" option\n");
336		dmxLog(dmxError,
337		       "are valid on all back-end servers.  See the Xdmx man "
338		       "page for\n");
339		dmxLog(dmxError,
340		       "more information on font paths.\n");
341		dmxFreeFontPath(fp);
342		XFreeFontPath(oldFontPath);
343		free(goodfps);
344		return FALSE;
345	    }
346
347	    newfp = malloc(len * sizeof(*newfp));
348	    for (i = 0; i < npaths; i++) {
349		if (goodfps[i]) {
350		    int n = strlen(fp[i]);
351		    newfp[j++] = n;
352		    strncpy(&newfp[j], fp[i], n);
353		    j += n;
354		}
355	    }
356
357	    if (SetFontPath(serverClient, newnpaths, (unsigned char *)newfp)) {
358		/* Note that this should never happen since all of the
359		 * FPEs were previously valid. */
360		dmxLog(dmxError, "Cannot reset the default font path.\n");
361	    }
362	} else if (dmxFontPath) {
363	    dmxLog(dmxError,
364		   "Please remove these font paths from the command line "
365		   "or\n");
366	    dmxLog(dmxError,
367		   "configuration file, or set the \"-ignorebadfontpaths\" "
368		   "option to\n");
369	    dmxLog(dmxError,
370		   "ignore them.  For more information on these options, see "
371		   "the\n");
372	    dmxLog(dmxError,
373		   "Xdmx man page.\n");
374	} else {
375	    dmxLog(dmxError,
376		   "Please specify the font paths that are available on all "
377		   "back-end\n");
378	    dmxLog(dmxError,
379		   "servers with the \"-fontpath\" option, or use the "
380                   "\"-ignorebadfontpaths\"\n");
381            dmxLog(dmxError,
382                   "to ignore bad defaults.  For more information on "
383                   "these and other\n");
384	    dmxLog(dmxError,
385		   "font-path-related options, see the Xdmx man page.\n");
386	}
387
388	if (!dmxIgnoreBadFontPaths ||
389	    (dmxIgnoreBadFontPaths && dmxSetFontPath(dmxScreen))) {
390	    /* We still have errors so return with error */
391	    dmxFreeFontPath(fp);
392	    XFreeFontPath(oldFontPath);
393	    free(goodfps);
394	    return FALSE;
395	}
396    }
397
398    /* Find requested font on back-end server */
399    name_atom = MakeAtom("FONT", 4, TRUE);
400    value_atom = 0L;
401
402    for (i = 0; i < pFont->info.nprops; i++) {
403	if ((Atom)pFont->info.props[i].name == name_atom) {
404	    value_atom = pFont->info.props[i].value;
405	    break;
406	}
407    }
408    if (!value_atom) return FALSE;
409
410    name = NameForAtom(value_atom);
411    if (!name) return FALSE;
412
413    pFontPriv->font[pScreen->myNum] =
414	XLoadQueryFont(dmxScreen->beDisplay, name);
415
416    /* Restore old font path */
417    XSetFontPath(dmxScreen->beDisplay, oldFontPath, nOldPaths);
418    XFreeFontPath(oldFontPath);
419    dmxSync(dmxScreen, FALSE);
420
421    if (!pFontPriv->font[pScreen->myNum]) return FALSE;
422
423    return TRUE;
424}
425
426/** Realize the font, \a pFont, on the back-end server associated with
427 *  \a pScreen. */
428Bool dmxRealizeFont(ScreenPtr pScreen, FontPtr pFont)
429{
430    DMXScreenInfo  *dmxScreen = &dmxScreens[pScreen->myNum];
431    dmxFontPrivPtr  pFontPriv;
432
433    if (!(pFontPriv = FontGetPrivate(pFont, dmxFontPrivateIndex))) {
434	FontSetPrivate(pFont, dmxFontPrivateIndex, NULL);
435	pFontPriv = malloc(sizeof(dmxFontPrivRec));
436	if (!pFontPriv) return FALSE;
437        pFontPriv->font = NULL;
438        MAXSCREENSALLOC(pFontPriv->font);
439        if (!pFontPriv->font) {
440            free(pFontPriv);
441            return FALSE;
442        }
443	pFontPriv->refcnt = 0;
444    }
445
446    FontSetPrivate(pFont, dmxFontPrivateIndex, (pointer)pFontPriv);
447
448    if (dmxScreen->beDisplay) {
449	if (!dmxBELoadFont(pScreen, pFont))
450	    return FALSE;
451
452	pFontPriv->refcnt++;
453    } else {
454	pFontPriv->font[pScreen->myNum] = NULL;
455    }
456
457    return TRUE;
458}
459
460/** Free \a pFont on the back-end associated with \a pScreen. */
461Bool dmxBEFreeFont(ScreenPtr pScreen, FontPtr pFont)
462{
463    DMXScreenInfo  *dmxScreen = &dmxScreens[pScreen->myNum];
464    dmxFontPrivPtr  pFontPriv = FontGetPrivate(pFont, dmxFontPrivateIndex);
465
466    if (pFontPriv && pFontPriv->font[pScreen->myNum]) {
467	XFreeFont(dmxScreen->beDisplay, pFontPriv->font[pScreen->myNum]);
468	pFontPriv->font[pScreen->myNum] = NULL;
469	return TRUE;
470    }
471
472    return FALSE;
473}
474
475/** Unrealize the font, \a pFont, on the back-end server associated with
476 *  \a pScreen. */
477Bool dmxUnrealizeFont(ScreenPtr pScreen, FontPtr pFont)
478{
479    DMXScreenInfo  *dmxScreen = &dmxScreens[pScreen->myNum];
480    dmxFontPrivPtr  pFontPriv;
481
482    if ((pFontPriv = FontGetPrivate(pFont, dmxFontPrivateIndex))) {
483	/* In case the font failed to load properly */
484	if (!pFontPriv->refcnt) {
485            MAXSCREENSFREE(pFontPriv->font);
486	    free(pFontPriv);
487	    FontSetPrivate(pFont, dmxFontPrivateIndex, NULL);
488	} else if (pFontPriv->font[pScreen->myNum]) {
489	    if (dmxScreen->beDisplay)
490		dmxBEFreeFont(pScreen, pFont);
491
492	    /* The code below is non-obvious, so here's an explanation...
493	     *
494	     * When creating the default GC, the server opens up the
495	     * default font once for each screen, which in turn calls
496	     * the RealizeFont function pointer once for each screen.
497	     * During this process both dix's font refcnt and DMX's font
498	     * refcnt are incremented once for each screen.
499	     *
500	     * Later, when shutting down the X server, dix shuts down
501	     * each screen in reverse order.  During this shutdown
502	     * procedure, each screen's default GC is freed and then
503	     * that screen is closed by calling the CloseScreen function
504	     * pointer.  screenInfo.numScreens is then decremented after
505	     * closing each screen.  This procedure means that the dix's
506	     * font refcnt for the font used by the default GC's is
507	     * decremented once for each screen # greater than 0.
508	     * However, since dix's refcnt for the default font is not
509	     * yet 0 for each screen greater than 0, no call to the
510	     * UnrealizeFont function pointer is made for those screens.
511	     * Then, when screen 0 is being closed, dix's font refcnt
512	     * for the default GC's font is finally 0 and the font is
513	     * unrealized.  However, since screenInfo.numScreens has
514	     * been decremented already down to 1, only one call to
515	     * UnrealizeFont is made (for screen 0).  Thus, even though
516	     * RealizeFont was called once for each screen,
517	     * UnrealizeFont is only called for screen 0.
518	     *
519	     * This is a bug in dix.
520	     *
521	     * To avoid the memory leak of pFontPriv for each server
522	     * generation, we can also free pFontPriv if the refcnt is
523	     * not yet 0 but the # of screens is 1 -- i.e., the case
524	     * described in the dix bug above.  This is only a temporary
525	     * workaround until the bug in dix is solved.
526	     *
527	     * The other problem is that the font structure allocated by
528	     * XLoadQueryFont() above is not freed for screens > 0.
529	     * This problem cannot be worked around here since the back-
530	     * end displays for screens > 0 have already been closed by
531	     * the time this code is called from dix.
532	     *
533	     * When the bug in dix described above is fixed, then we can
534	     * remove the "|| screenInfo.numScreens == 1" code below and
535	     * the memory leaks will be eliminated.
536	     */
537	    if (--pFontPriv->refcnt == 0
538#if 1
539		/* Remove this code when the dix bug is fixed */
540		|| screenInfo.numScreens == 1
541#endif
542		) {
543                MAXSCREENSFREE(pFontPriv->font);
544		free(pFontPriv);
545		FontSetPrivate(pFont, dmxFontPrivateIndex, NULL);
546	    }
547	}
548    }
549
550    return TRUE;
551}
552