1/************************************************************
2Copyright (c) 1993 by Silicon Graphics Computer Systems, Inc.
3
4Permission to use, copy, modify, and distribute this
5software and its documentation for any purpose and without
6fee is hereby granted, provided that the above copyright
7notice appear in all copies and that both that copyright
8notice and this permission notice appear in supporting
9documentation, and that the name of Silicon Graphics not be
10used in advertising or publicity pertaining to distribution
11of the software without specific prior written permission.
12Silicon Graphics makes no representation about the suitability
13of this software for any purpose. It is provided "as is"
14without any express or implied warranty.
15
16SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
19GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
20DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
22OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
23THE USE OR PERFORMANCE OF THIS SOFTWARE.
24
25********************************************************/
26
27#ifdef HAVE_DIX_CONFIG_H
28#include <dix-config.h>
29#endif
30
31#include <xkb-config.h>
32
33#include <stdio.h>
34#include <ctype.h>
35#include <X11/X.h>
36#include <X11/Xos.h>
37#include <X11/Xproto.h>
38#include <X11/keysym.h>
39#include <X11/extensions/XKM.h>
40#include "inputstr.h"
41#include "scrnintstr.h"
42#include "windowstr.h"
43#define	XKBSRV_NEED_FILE_FUNCS
44#include <xkbsrv.h>
45#include <X11/extensions/XI.h>
46#include "xkb.h"
47
48	/*
49	 * If XKM_OUTPUT_DIR specifies a path without a leading slash, it is
50	 * relative to the top-level XKB configuration directory.
51	 * Making the server write to a subdirectory of that directory
52	 * requires some work in the general case (install procedure
53	 * has to create links to /var or somesuch on many machines),
54	 * so we just compile into /usr/tmp for now.
55	 */
56#ifndef XKM_OUTPUT_DIR
57#define	XKM_OUTPUT_DIR	"compiled/"
58#endif
59
60#define	PRE_ERROR_MSG "\"The XKEYBOARD keymap compiler (xkbcomp) reports:\""
61#define	ERROR_PREFIX	"\"> \""
62#define	POST_ERROR_MSG1 "\"Errors from xkbcomp are not fatal to the X server\""
63#define	POST_ERROR_MSG2 "\"End of messages from xkbcomp\""
64
65#if defined(WIN32)
66#define PATHSEPARATOR "\\"
67#else
68#define PATHSEPARATOR "/"
69#endif
70
71#ifdef WIN32
72
73#include <X11/Xwindows.h>
74const char*
75Win32TempDir()
76{
77    static char buffer[PATH_MAX];
78    if (GetTempPath(sizeof(buffer), buffer))
79    {
80        int len;
81        buffer[sizeof(buffer)-1] = 0;
82        len = strlen(buffer);
83        if (len > 0)
84            if (buffer[len-1] == '\\')
85                buffer[len-1] = 0;
86        return buffer;
87    }
88    if (getenv("TEMP") != NULL)
89        return getenv("TEMP");
90    else if (getenv("TMP") != NULL)
91        return getenv("TEMP");
92    else
93        return "/tmp";
94}
95
96int
97Win32System(const char *cmdline)
98{
99    STARTUPINFO si;
100    PROCESS_INFORMATION pi;
101    DWORD dwExitCode;
102    char *cmd = strdup(cmdline);
103
104    ZeroMemory( &si, sizeof(si) );
105    si.cb = sizeof(si);
106    ZeroMemory( &pi, sizeof(pi) );
107
108    if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
109    {
110	LPVOID buffer;
111	if (!FormatMessage(
112		    FORMAT_MESSAGE_ALLOCATE_BUFFER |
113		    FORMAT_MESSAGE_FROM_SYSTEM |
114		    FORMAT_MESSAGE_IGNORE_INSERTS,
115		    NULL,
116		    GetLastError(),
117		    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
118		    (LPTSTR) &buffer,
119		    0,
120		    NULL ))
121	{
122	    ErrorF("[xkb] Starting '%s' failed!\n", cmdline);
123	}
124	else
125	{
126	    ErrorF("[xkb] Starting '%s' failed: %s", cmdline, (char *)buffer);
127	    LocalFree(buffer);
128	}
129
130	free(cmd);
131	return -1;
132    }
133    /* Wait until child process exits. */
134    WaitForSingleObject( pi.hProcess, INFINITE );
135
136    GetExitCodeProcess( pi.hProcess, &dwExitCode);
137
138    /* Close process and thread handles. */
139    CloseHandle( pi.hProcess );
140    CloseHandle( pi.hThread );
141    free(cmd);
142
143    return dwExitCode;
144}
145#undef System
146#define System(x) Win32System(x)
147#endif
148
149static void
150OutputDirectory(
151    char* outdir,
152    size_t size)
153{
154#ifndef WIN32
155    /* Can we write an xkm and then open it too? */
156    if (access(XKM_OUTPUT_DIR, W_OK | X_OK) == 0 && (strlen(XKM_OUTPUT_DIR) < size))
157    {
158	(void) strcpy (outdir, XKM_OUTPUT_DIR);
159    } else
160#else
161    if (strlen(Win32TempDir()) + 1 < size)
162    {
163	(void) strcpy(outdir, Win32TempDir());
164	(void) strcat(outdir, "\\");
165    } else
166#endif
167    if (strlen("/tmp/") < size)
168    {
169	(void) strcpy (outdir, "/tmp/");
170    }
171}
172
173static Bool
174XkbDDXCompileKeymapByNames(	XkbDescPtr		xkb,
175				XkbComponentNamesPtr	names,
176				unsigned		want,
177				unsigned		need,
178				char *			nameRtrn,
179				int			nameRtrnLen)
180{
181    FILE *	out;
182    char	*buf = NULL, keymap[PATH_MAX], xkm_output_dir[PATH_MAX];
183
184    const char	*emptystring = "";
185    char *xkbbasedirflag = NULL;
186    const char	*xkbbindir = emptystring;
187    const char	*xkbbindirsep = emptystring;
188
189#ifdef WIN32
190    /* WIN32 has no popen. The input must be stored in a file which is
191       used as input for xkbcomp. xkbcomp does not read from stdin. */
192    char tmpname[PATH_MAX];
193    const char *xkmfile = tmpname;
194#else
195    const char *xkmfile = "-";
196#endif
197
198    snprintf(keymap, sizeof(keymap), "server-%s", display);
199
200    OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir));
201
202#ifdef WIN32
203    strcpy(tmpname, Win32TempDir());
204    strcat(tmpname, "\\xkb_XXXXXX");
205    (void) mktemp(tmpname);
206#endif
207
208    if (XkbBaseDirectory != NULL) {
209	if (asprintf(&xkbbasedirflag, "\"-R%s\"", XkbBaseDirectory) == -1)
210	    xkbbasedirflag = NULL;
211    }
212
213    if (XkbBinDirectory != NULL) {
214	int ld = strlen(XkbBinDirectory);
215	int lps = strlen(PATHSEPARATOR);
216
217	xkbbindir = XkbBinDirectory;
218
219	if ((ld >= lps) &&
220	    (strcmp(xkbbindir + ld - lps, PATHSEPARATOR) != 0)) {
221	    xkbbindirsep = PATHSEPARATOR;
222	}
223    }
224
225    if (asprintf(&buf,
226		 "\"%s%sxkbcomp\" -w %d %s -xkm \"%s\" "
227		  "-em1 %s -emp %s -eml %s \"%s%s.xkm\"",
228		 xkbbindir, xkbbindirsep,
229		 ((xkbDebugFlags < 2) ? 1 :
230		  ((xkbDebugFlags > 10) ? 10 : (int) xkbDebugFlags)),
231		 xkbbasedirflag ? xkbbasedirflag : "", xkmfile,
232		 PRE_ERROR_MSG, ERROR_PREFIX, POST_ERROR_MSG1,
233		 xkm_output_dir, keymap) == -1)
234	buf = NULL;
235
236    free(xkbbasedirflag);
237
238    if (!buf) {
239        LogMessage(X_ERROR, "XKB: Could not invoke xkbcomp: not enough memory\n");
240        return FALSE;
241    }
242
243#ifndef WIN32
244    out= Popen(buf,"w");
245#else
246    out= fopen(tmpname, "w");
247#endif
248
249    if (out!=NULL) {
250#ifdef DEBUG
251    if (xkbDebugFlags) {
252       ErrorF("[xkb] XkbDDXCompileKeymapByNames compiling keymap:\n");
253       XkbWriteXKBKeymapForNames(stderr,names,xkb,want,need);
254    }
255#endif
256	XkbWriteXKBKeymapForNames(out,names,xkb,want,need);
257#ifndef WIN32
258	if (Pclose(out)==0)
259#else
260	if (fclose(out)==0 && System(buf) >= 0)
261#endif
262	{
263            if (xkbDebugFlags)
264                DebugF("[xkb] xkb executes: %s\n",buf);
265	    if (nameRtrn) {
266		strncpy(nameRtrn,keymap,nameRtrnLen);
267		nameRtrn[nameRtrnLen-1]= '\0';
268	    }
269            free(buf);
270	    return TRUE;
271	}
272	else
273	    LogMessage(X_ERROR, "Error compiling keymap (%s)\n", keymap);
274#ifdef WIN32
275        /* remove the temporary file */
276        unlink(tmpname);
277#endif
278    }
279    else {
280#ifndef WIN32
281	LogMessage(X_ERROR, "XKB: Could not invoke xkbcomp\n");
282#else
283	LogMessage(X_ERROR, "Could not open file %s\n", tmpname);
284#endif
285    }
286    if (nameRtrn)
287	nameRtrn[0]= '\0';
288    free(buf);
289    return FALSE;
290}
291
292static FILE *
293XkbDDXOpenConfigFile(char *mapName,char *fileNameRtrn,int fileNameRtrnLen)
294{
295char	buf[PATH_MAX],xkm_output_dir[PATH_MAX];
296FILE *	file;
297
298    buf[0]= '\0';
299    if (mapName!=NULL) {
300	OutputDirectory(xkm_output_dir, sizeof(xkm_output_dir));
301	if ((XkbBaseDirectory!=NULL)&&(xkm_output_dir[0]!='/')
302#ifdef WIN32
303                &&(!isalpha(xkm_output_dir[0]) || xkm_output_dir[1]!=':')
304#endif
305                ) {
306	    if (strlen(XkbBaseDirectory)+strlen(xkm_output_dir)
307		     +strlen(mapName)+6 <= PATH_MAX)
308	    {
309	        sprintf(buf,"%s/%s%s.xkm",XkbBaseDirectory,
310					xkm_output_dir,mapName);
311	    }
312	}
313	else if (strlen(xkm_output_dir)+strlen(mapName)+5 <= PATH_MAX)
314	    sprintf(buf,"%s%s.xkm",xkm_output_dir,mapName);
315	if (buf[0] != '\0')
316	    file= fopen(buf,"rb");
317	else file= NULL;
318    }
319    else file= NULL;
320    if ((fileNameRtrn!=NULL)&&(fileNameRtrnLen>0)) {
321	strncpy(fileNameRtrn,buf,fileNameRtrnLen);
322	buf[fileNameRtrnLen-1]= '\0';
323    }
324    return file;
325}
326
327unsigned
328XkbDDXLoadKeymapByNames(	DeviceIntPtr		keybd,
329				XkbComponentNamesPtr	names,
330				unsigned		want,
331				unsigned		need,
332				XkbDescPtr *		xkbRtrn,
333				char *			nameRtrn,
334				int 			nameRtrnLen)
335{
336XkbDescPtr      xkb;
337FILE	*	file;
338char		fileName[PATH_MAX];
339unsigned	missing;
340
341    *xkbRtrn = NULL;
342    if ((keybd==NULL)||(keybd->key==NULL)||(keybd->key->xkbInfo==NULL))
343	 xkb= NULL;
344    else xkb= keybd->key->xkbInfo->desc;
345    if ((names->keycodes==NULL)&&(names->types==NULL)&&
346	(names->compat==NULL)&&(names->symbols==NULL)&&
347	(names->geometry==NULL)) {
348        LogMessage(X_ERROR, "XKB: No components provided for device %s\n",
349                   keybd->name ? keybd->name : "(unnamed keyboard)");
350        return 0;
351    }
352    else if (!XkbDDXCompileKeymapByNames(xkb,names,want,need,
353                                         nameRtrn,nameRtrnLen)){
354	LogMessage(X_ERROR, "XKB: Couldn't compile keymap\n");
355	return 0;
356    }
357    file= XkbDDXOpenConfigFile(nameRtrn,fileName,PATH_MAX);
358    if (file==NULL) {
359	LogMessage(X_ERROR, "Couldn't open compiled keymap file %s\n",fileName);
360	return 0;
361    }
362    missing= XkmReadFile(file,need,want,xkbRtrn);
363    if (*xkbRtrn==NULL) {
364	LogMessage(X_ERROR, "Error loading keymap %s\n",fileName);
365	fclose(file);
366	(void) unlink (fileName);
367	return 0;
368    }
369    else {
370	DebugF("Loaded XKB keymap %s, defined=0x%x\n",fileName,(*xkbRtrn)->defined);
371    }
372    fclose(file);
373    (void) unlink (fileName);
374    return (need|want)&(~missing);
375}
376
377Bool
378XkbDDXNamesFromRules(	DeviceIntPtr		keybd,
379			char *			rules_name,
380			XkbRF_VarDefsPtr	defs,
381			XkbComponentNamesPtr	names)
382{
383char 		buf[PATH_MAX];
384FILE *		file;
385Bool		complete;
386XkbRF_RulesPtr	rules;
387
388    if (!rules_name)
389	return FALSE;
390
391    if (strlen(XkbBaseDirectory) + strlen(rules_name) + 8 > PATH_MAX) {
392        LogMessage(X_ERROR, "XKB: Rules name is too long\n");
393        return FALSE;
394    }
395    sprintf(buf,"%s/rules/%s", XkbBaseDirectory, rules_name);
396
397    file = fopen(buf, "r");
398    if (!file) {
399        LogMessage(X_ERROR, "XKB: Couldn't open rules file %s\n", buf);
400	return FALSE;
401    }
402
403    rules = XkbRF_Create();
404    if (!rules) {
405        LogMessage(X_ERROR, "XKB: Couldn't create rules struct\n");
406	fclose(file);
407	return FALSE;
408    }
409
410    if (!XkbRF_LoadRules(file, rules)) {
411        LogMessage(X_ERROR, "XKB: Couldn't parse rules file %s\n", rules_name);
412	fclose(file);
413	XkbRF_Free(rules,TRUE);
414	return FALSE;
415    }
416
417    memset(names, 0, sizeof(*names));
418    complete = XkbRF_GetComponents(rules,defs,names);
419    fclose(file);
420    XkbRF_Free(rules, TRUE);
421
422    if (!complete)
423        LogMessage(X_ERROR, "XKB: Rules returned no components\n");
424
425    return complete;
426}
427
428static Bool
429XkbRMLVOtoKcCGST(DeviceIntPtr dev, XkbRMLVOSet *rmlvo, XkbComponentNamesPtr kccgst)
430{
431    XkbRF_VarDefsRec mlvo;
432
433    mlvo.model = rmlvo->model;
434    mlvo.layout = rmlvo->layout;
435    mlvo.variant = rmlvo->variant;
436    mlvo.options = rmlvo->options;
437
438    return XkbDDXNamesFromRules(dev, rmlvo->rules, &mlvo, kccgst);
439}
440
441/**
442 * Compile the given RMLVO keymap and return it. Returns the XkbDescPtr on
443 * success or NULL on failure. If the components compiled are not a superset
444 * or equal to need, the compiliation is treated as failure.
445 */
446static XkbDescPtr
447XkbCompileKeymapForDevice(DeviceIntPtr dev, XkbRMLVOSet *rmlvo, int need)
448{
449    XkbDescPtr xkb = NULL;
450    unsigned int provided;
451    XkbComponentNamesRec kccgst = {0};
452    char name[PATH_MAX];
453
454    if (XkbRMLVOtoKcCGST(dev, rmlvo, &kccgst)) {
455        provided = XkbDDXLoadKeymapByNames(dev, &kccgst, XkmAllIndicesMask, need,
456                                           &xkb, name, PATH_MAX);
457        if ((need & provided) != need) {
458            if (xkb) {
459                XkbFreeKeyboard(xkb, 0, TRUE);
460                xkb = NULL;
461            }
462        }
463    }
464
465    XkbFreeComponentNames(&kccgst, FALSE);
466    return xkb;
467}
468
469XkbDescPtr
470XkbCompileKeymap(DeviceIntPtr dev, XkbRMLVOSet *rmlvo)
471{
472    XkbDescPtr xkb;
473    unsigned int need;
474
475    if (!dev || !rmlvo) {
476        LogMessage(X_ERROR, "XKB: No device or RMLVO specified\n");
477        return NULL;
478    }
479
480    /* These are the components we really really need */
481    need = XkmSymbolsMask | XkmCompatMapMask | XkmTypesMask |
482           XkmKeyNamesMask | XkmVirtualModsMask;
483
484
485    xkb = XkbCompileKeymapForDevice(dev, rmlvo, need);
486
487    if (!xkb) {
488        XkbRMLVOSet dflts;
489
490        /* we didn't get what we really needed. And that will likely leave
491         * us with a keyboard that doesn't work. Use the defaults instead */
492        LogMessage(X_ERROR, "XKB: Failed to load keymap. Loading default "
493                   "keymap instead.\n");
494
495        XkbGetRulesDflts(&dflts);
496
497        xkb = XkbCompileKeymapForDevice(dev, &dflts, 0);
498
499        XkbFreeRMLVOSet(&dflts, FALSE);
500    }
501
502    return xkb;
503}
504