#include <X11/Xos.h>
#include "xkbcomp.h"
#include "tokens.h"
#include "expr.h"
#include "vmod.h"
#include "misc.h"
#include "indicators.h"
#include "action.h"
#include "compat.h"
typedef struct _SymInterpInfo
{
CommonInfo defs;
XkbSymInterpretRec interp;
} SymInterpInfo;
#define _SI_VirtualMod (1<<0)
#define _SI_Action (1<<1)
#define _SI_AutoRepeat (1<<2)
#define _SI_LockingKey (1<<3)
#define _SI_LevelOneOnly (1<<4)
typedef struct _GroupCompatInfo
{
unsigned char fileID;
unsigned char merge;
unsigned char real_mods;
unsigned short vmods;
} GroupCompatInfo;
typedef struct _CompatInfo
{
char *name;
unsigned fileID;
int errorCount;
int nInterps;
SymInterpInfo *interps;
SymInterpInfo dflt;
LEDInfo ledDflt;
GroupCompatInfo groupCompat[XkbNumKbdGroups];
LEDInfo *leds;
VModInfo vmods;
ActionInfo *act;
XkbDescPtr xkb;
} CompatInfo;
#define ReportSINotArray(si,f,i) \
ReportNotArray("symbol interpretation",(f),siText((si),(i)))
#define ReportSIBadType(si,f,w,i) \
ReportBadType("symbol interpretation",(f),siText((si),(i)),(w))
static char *
siText(SymInterpInfo * si, CompatInfo * info)
{
static char buf[128];
if (si == &info->dflt)
{
snprintf(buf, sizeof(buf), "default");
}
else
{
snprintf(buf, sizeof(buf), "%s+%s(%s)",
XkbKeysymText(si->interp.sym, XkbMessage),
XkbSIMatchText(si->interp.match, XkbMessage),
XkbModMaskText(si->interp.mods, XkbMessage));
}
return buf;
}
static void
InitCompatInfo(CompatInfo * info, XkbDescPtr xkb)
{
register int i;
info->xkb = xkb;
info->name = NULL;
info->fileID = 0;
info->errorCount = 0;
info->nInterps = 0;
info->interps = NULL;
info->act = NULL;
info->dflt.defs.fileID = info->fileID;
info->dflt.defs.defined = 0;
info->dflt.defs.merge = MergeOverride;
info->dflt.interp.flags = 0;
info->dflt.interp.virtual_mod = XkbNoModifier;
info->dflt.interp.act.type = XkbSA_NoAction;
for (i = 0; i < XkbAnyActionDataSize; i++)
{
info->dflt.interp.act.data[i] = 0;
}
ClearIndicatorMapInfo(xkb->dpy, &info->ledDflt);
info->ledDflt.defs.fileID = info->fileID;
info->ledDflt.defs.defined = 0;
info->ledDflt.defs.merge = MergeOverride;
bzero((char *) &info->groupCompat[0],
XkbNumKbdGroups * sizeof(GroupCompatInfo));
info->leds = NULL;
InitVModInfo(&info->vmods, xkb);
return;
}
static void
ClearCompatInfo(CompatInfo * info, XkbDescPtr xkb)
{
register int i;
if (info->name != NULL)
uFree(info->name);
info->name = NULL;
info->dflt.defs.defined = 0;
info->dflt.defs.merge = MergeAugment;
info->dflt.interp.flags = 0;
info->dflt.interp.virtual_mod = XkbNoModifier;
info->dflt.interp.act.type = XkbSA_NoAction;
for (i = 0; i < XkbAnyActionDataSize; i++)
{
info->dflt.interp.act.data[i] = 0;
}
ClearIndicatorMapInfo(xkb->dpy, &info->ledDflt);
info->nInterps = 0;
info->interps = (SymInterpInfo *) ClearCommonInfo(&info->interps->defs);
bzero((char *) &info->groupCompat[0],
XkbNumKbdGroups * sizeof(GroupCompatInfo));
info->leds = (LEDInfo *) ClearCommonInfo(&info->leds->defs);
ClearVModInfo(&info->vmods, xkb);
return;
}
static SymInterpInfo *
NextInterp(CompatInfo * info)
{
SymInterpInfo *si;
si = uTypedAlloc(SymInterpInfo);
if (si)
{
bzero((char *) si, sizeof(SymInterpInfo));
info->interps =
(SymInterpInfo *) AddCommonInfo(&info->interps->defs,
(CommonInfo *) si);
info->nInterps++;
}
return si;
}
static SymInterpInfo *
FindMatchingInterp(CompatInfo * info, SymInterpInfo * new)
{
SymInterpInfo *old;
for (old = info->interps; old != NULL;
old = (SymInterpInfo *) old->defs.next)
{
if ((old->interp.sym == new->interp.sym) &&
(old->interp.mods == new->interp.mods) &&
(old->interp.match == new->interp.match))
{
return old;
}
}
return NULL;
}
static Bool
AddInterp(CompatInfo * info, SymInterpInfo * new)
{
unsigned collide;
SymInterpInfo *old;
collide = 0;
old = FindMatchingInterp(info, new);
if (old != NULL)
{
if (new->defs.merge == MergeReplace)
{
SymInterpInfo *next = (SymInterpInfo *) old->defs.next;
if (((old->defs.fileID == new->defs.fileID)
&& (warningLevel > 0)) || (warningLevel > 9))
{
WARN1("Multiple definitions for \"%s\"\n", siText(new, info));
ACTION("Earlier interpretation ignored\n");
}
*old = *new;
old->defs.next = &next->defs;
return True;
}
if (UseNewField(_SI_VirtualMod, &old->defs, &new->defs, &collide))
{
old->interp.virtual_mod = new->interp.virtual_mod;
old->defs.defined |= _SI_VirtualMod;
}
if (UseNewField(_SI_Action, &old->defs, &new->defs, &collide))
{
old->interp.act = new->interp.act;
old->defs.defined |= _SI_Action;
}
if (UseNewField(_SI_AutoRepeat, &old->defs, &new->defs, &collide))
{
old->interp.flags &= ~XkbSI_AutoRepeat;
old->interp.flags |= (new->interp.flags & XkbSI_AutoRepeat);
old->defs.defined |= _SI_AutoRepeat;
}
if (UseNewField(_SI_LockingKey, &old->defs, &new->defs, &collide))
{
old->interp.flags &= ~XkbSI_LockingKey;
old->interp.flags |= (new->interp.flags & XkbSI_LockingKey);
old->defs.defined |= _SI_LockingKey;
}
if (UseNewField(_SI_LevelOneOnly, &old->defs, &new->defs, &collide))
{
old->interp.match &= ~XkbSI_LevelOneOnly;
old->interp.match |= (new->interp.match & XkbSI_LevelOneOnly);
old->defs.defined |= _SI_LevelOneOnly;
}
if (collide)
{
WARN1("Multiple interpretations of \"%s\"\n", siText(new, info));
ACTION1("Using %s definition for duplicate fields\n",
(new->defs.merge != MergeAugment ? "last" : "first"));
}
return True;
}
old = new;
if ((new = NextInterp(info)) == NULL)
return False;
*new = *old;
new->defs.next = NULL;
return True;
}
static Bool
AddGroupCompat(CompatInfo * info, unsigned group, GroupCompatInfo * newGC)
{
GroupCompatInfo *gc;
unsigned merge;
merge = newGC->merge;
gc = &info->groupCompat[group];
if (((gc->real_mods == newGC->real_mods) && (gc->vmods == newGC->vmods)))
{
return True;
}
if (((gc->fileID == newGC->fileID) && (warningLevel > 0))
|| (warningLevel > 9))
{
WARN1("Compat map for group %d redefined\n", group + 1);
ACTION1("Using %s definition\n",
(merge == MergeAugment ? "old" : "new"));
}
if (merge != MergeAugment)
*gc = *newGC;
return True;
}
static Bool
ResolveStateAndPredicate(ExprDef * expr,
unsigned *pred_rtrn,
unsigned *mods_rtrn, CompatInfo * info)
{
ExprResult result;
if (expr == NULL)
{
*pred_rtrn = XkbSI_AnyOfOrNone;
*mods_rtrn = ~0;
return True;
}
*pred_rtrn = XkbSI_Exactly;
if (expr->op == ExprActionDecl)
{
char *pred_txt =
XkbAtomText(NULL, expr->value.action.name, XkbMessage);
if (uStrCaseCmp(pred_txt, "noneof") == 0)
*pred_rtrn = XkbSI_NoneOf;
else if (uStrCaseCmp(pred_txt, "anyofornone") == 0)
*pred_rtrn = XkbSI_AnyOfOrNone;
else if (uStrCaseCmp(pred_txt, "anyof") == 0)
*pred_rtrn = XkbSI_AnyOf;
else if (uStrCaseCmp(pred_txt, "allof") == 0)
*pred_rtrn = XkbSI_AllOf;
else if (uStrCaseCmp(pred_txt, "exactly") == 0)
*pred_rtrn = XkbSI_Exactly;
else
{
ERROR1("Illegal modifier predicate \"%s\"\n", pred_txt);
ACTION("Ignored\n");
return False;
}
expr = expr->value.action.args;
}
else if (expr->op == ExprIdent)
{
char *pred_txt = XkbAtomText(NULL, expr->value.str, XkbMessage);
if ((pred_txt) && (uStrCaseCmp(pred_txt, "any") == 0))
{
*pred_rtrn = XkbSI_AnyOf;
*mods_rtrn = 0xff;
return True;
}
}
if (ExprResolveModMask(expr, &result, NULL, NULL))
{
*mods_rtrn = result.uval;
return True;
}
return False;
}
static void
MergeIncludedCompatMaps(CompatInfo * into, CompatInfo * from, unsigned merge)
{
SymInterpInfo *si;
LEDInfo *led, *rtrn, *next;
GroupCompatInfo *gcm;
register int i;
if (from->errorCount > 0)
{
into->errorCount += from->errorCount;
return;
}
if (into->name == NULL)
{
into->name = from->name;
from->name = NULL;
}
for (si = from->interps; si; si = (SymInterpInfo *) si->defs.next)
{
if (merge != MergeDefault)
si->defs.merge = merge;
if (!AddInterp(into, si))
into->errorCount++;
}
for (i = 0, gcm = &from->groupCompat[0]; i < XkbNumKbdGroups; i++, gcm++)
{
if (merge != MergeDefault)
gcm->merge = merge;
if (!AddGroupCompat(into, i, gcm))
into->errorCount++;
}
for (led = from->leds; led != NULL; led = next)
{
next = (LEDInfo *) led->defs.next;
if (merge != MergeDefault)
led->defs.merge = merge;
rtrn = AddIndicatorMap(into->leds, led);
if (rtrn != NULL)
into->leds = rtrn;
else
into->errorCount++;
}
return;
}
typedef void (*FileHandler) (XkbFile * ,
XkbDescPtr ,
unsigned ,
CompatInfo *
);
static Bool
HandleIncludeCompatMap(IncludeStmt * stmt,
XkbDescPtr xkb, CompatInfo * info, FileHandler hndlr)
{
unsigned newMerge;
XkbFile *rtrn;
CompatInfo included;
Bool haveSelf;
haveSelf = False;
if ((stmt->file == NULL) && (stmt->map == NULL))
{
haveSelf = True;
included = *info;
bzero(info, sizeof(CompatInfo));
}
else if (ProcessIncludeFile(stmt, XkmCompatMapIndex, &rtrn, &newMerge))
{
InitCompatInfo(&included, xkb);
included.fileID = rtrn->id;
included.dflt = info->dflt;
included.dflt.defs.fileID = rtrn->id;
included.dflt.defs.merge = newMerge;
included.ledDflt.defs.fileID = rtrn->id;
included.ledDflt.defs.merge = newMerge;
included.act = info->act;
(*hndlr) (rtrn, xkb, MergeOverride, &included);
if (stmt->stmt != NULL)
{
if (included.name != NULL)
uFree(included.name);
included.name = stmt->stmt;
stmt->stmt = NULL;
}
}
else
{
info->errorCount += 10;
return False;
}
if ((stmt->next != NULL) && (included.errorCount < 1))
{
IncludeStmt *next;
unsigned op;
CompatInfo next_incl;
for (next = stmt->next; next != NULL; next = next->next)
{
if ((next->file == NULL) && (next->map == NULL))
{
haveSelf = True;
MergeIncludedCompatMaps(&included, info, next->merge);
ClearCompatInfo(info, xkb);
}
else if (ProcessIncludeFile(next, XkmCompatMapIndex, &rtrn, &op))
{
InitCompatInfo(&next_incl, xkb);
next_incl.fileID = rtrn->id;
next_incl.dflt = info->dflt;
next_incl.dflt.defs.fileID = rtrn->id;
next_incl.dflt.defs.merge = op;
next_incl.ledDflt.defs.fileID = rtrn->id;
next_incl.ledDflt.defs.merge = op;
next_incl.act = info->act;
(*hndlr) (rtrn, xkb, MergeOverride, &next_incl);
MergeIncludedCompatMaps(&included, &next_incl, op);
ClearCompatInfo(&next_incl, xkb);
}
else
{
info->errorCount += 10;
return False;
}
}
}
if (haveSelf)
*info = included;
else
{
MergeIncludedCompatMaps(info, &included, newMerge);
ClearCompatInfo(&included, xkb);
}
return (info->errorCount == 0);
}
static LookupEntry useModMapValues[] = {
{"levelone", 1},
{"level1", 1},
{"anylevel", 0},
{"any", 0},
{NULL, 0}
};
static int
SetInterpField(SymInterpInfo * si,
XkbDescPtr xkb,
char *field,
ExprDef * arrayNdx, ExprDef * value, CompatInfo * info)
{
int ok = 1;
ExprResult tmp;
if (uStrCaseCmp(field, "action") == 0)
{
if (arrayNdx != NULL)
return ReportSINotArray(si, field, info);
ok = HandleActionDef(value, xkb, &si->interp.act, si->defs.merge,
info->act);
if (ok)
si->defs.defined |= _SI_Action;
}
else if ((uStrCaseCmp(field, "virtualmodifier") == 0) ||
(uStrCaseCmp(field, "virtualmod") == 0))
{
if (arrayNdx != NULL)
return ReportSINotArray(si, field, info);
ok = ResolveVirtualModifier(value, &tmp, &info->vmods);
if (ok)
{
si->interp.virtual_mod = tmp.uval;
si->defs.defined |= _SI_VirtualMod;
}
else
return ReportSIBadType(si, field, "virtual modifier", info);
}
else if (uStrCaseCmp(field, "repeat") == 0)
{
if (arrayNdx != NULL)
return ReportSINotArray(si, field, info);
ok = ExprResolveBoolean(value, &tmp, NULL, NULL);
if (ok)
{
if (tmp.uval)
si->interp.flags |= XkbSI_AutoRepeat;
else
si->interp.flags &= ~XkbSI_AutoRepeat;
si->defs.defined |= _SI_AutoRepeat;
}
else
return ReportSIBadType(si, field, "boolean", info);
}
else if (uStrCaseCmp(field, "locking") == 0)
{
if (arrayNdx != NULL)
return ReportSINotArray(si, field, info);
ok = ExprResolveBoolean(value, &tmp, NULL, NULL);
if (ok)
{
if (tmp.uval)
si->interp.flags |= XkbSI_LockingKey;
else
si->interp.flags &= ~XkbSI_LockingKey;
si->defs.defined |= _SI_LockingKey;
}
else
return ReportSIBadType(si, field, "boolean", info);
}
else if ((uStrCaseCmp(field, "usemodmap") == 0) ||
(uStrCaseCmp(field, "usemodmapmods") == 0))
{
if (arrayNdx != NULL)
return ReportSINotArray(si, field, info);
ok = ExprResolveEnum(value, &tmp, useModMapValues);
if (ok)
{
if (tmp.uval)
si->interp.match |= XkbSI_LevelOneOnly;
else
si->interp.match &= ~XkbSI_LevelOneOnly;
si->defs.defined |= _SI_LevelOneOnly;
}
else
return ReportSIBadType(si, field, "level specification", info);
}
else
{
ok = ReportBadField("symbol interpretation", field, siText(si, info));
}
return ok;
}
LookupEntry groupNames[] = {
{"group1", 0x01}
,
{"group2", 0x02}
,
{"group3", 0x04}
,
{"group4", 0x08}
,
{"group5", 0x10}
,
{"group6", 0x20}
,
{"group7", 0x40}
,
{"group8", 0x80}
,
{"none", 0x00}
,
{"all", 0xff}
,
{NULL, 0}
};
static int
HandleInterpVar(VarDef * stmt, XkbDescPtr xkb, CompatInfo * info)
{
ExprResult elem, field;
ExprDef *ndx;
if (ExprResolveLhs(stmt->name, &elem, &field, &ndx) == 0)
return 0;
if (elem.str && (uStrCaseCmp(elem.str, "interpret") == 0))
return SetInterpField(&info->dflt, xkb, field.str, ndx, stmt->value,
info);
if (elem.str && (uStrCaseCmp(elem.str, "indicator") == 0))
{
return SetIndicatorMapField(&info->ledDflt, xkb, field.str, ndx,
stmt->value);
}
return SetActionField(xkb, elem.str, field.str, ndx, stmt->value,
&info->act);
}
static int
HandleInterpBody(VarDef * def, XkbDescPtr xkb, SymInterpInfo * si,
CompatInfo * info)
{
int ok = 1;
ExprResult tmp, field;
ExprDef *arrayNdx;
for (; def != NULL; def = (VarDef *) def->common.next)
{
if ((def->name) && (def->name->type == ExprFieldRef))
{
ok = HandleInterpVar(def, xkb, info);
continue;
}
ok = ExprResolveLhs(def->name, &tmp, &field, &arrayNdx);
if (ok)
ok = SetInterpField(si, xkb, field.str, arrayNdx, def->value,
info);
}
return ok;
}
static int
HandleInterpDef(InterpDef * def, XkbDescPtr xkb, unsigned merge,
CompatInfo * info)
{
unsigned pred, mods;
SymInterpInfo si;
if (!ResolveStateAndPredicate(def->match, &pred, &mods, info))
{
ERROR("Couldn't determine matching modifiers\n");
ACTION("Symbol interpretation ignored\n");
return False;
}
if (def->merge != MergeDefault)
merge = def->merge;
si = info->dflt;
si.defs.merge = merge;
si.interp.sym = def->sym;
si.interp.match = pred & XkbSI_OpMask;
si.interp.mods = mods;
if (!HandleInterpBody(def->def, xkb, &si, info))
{
info->errorCount++;
return False;
}
if (!AddInterp(info, &si))
{
info->errorCount++;
return False;
}
return True;
}
static int
HandleGroupCompatDef(GroupCompatDef * def,
XkbDescPtr xkb, unsigned merge, CompatInfo * info)
{
ExprResult val;
GroupCompatInfo tmp;
if (def->merge != MergeDefault)
merge = def->merge;
if (!XkbIsLegalGroup(def->group - 1))
{
ERROR1("Keyboard group must be in the range 1..%d\n",
XkbNumKbdGroups + 1);
ACTION1("Compatibility map for illegal group %d ignored\n",
def->group);
return False;
}
tmp.fileID = info->fileID;
tmp.merge = merge;
if (!ExprResolveModMask(def->def, &val, LookupVModMask, (XPointer) xkb))
{
ERROR("Expected a modifier mask in group compatibility definition\n");
ACTION1("Ignoring illegal compatibility map for group %d\n",
def->group);
return False;
}
tmp.real_mods = val.uval & 0xff;
tmp.vmods = (val.uval >> 8) & 0xffff;
return AddGroupCompat(info, def->group - 1, &tmp);
}
static void
HandleCompatMapFile(XkbFile * file,
XkbDescPtr xkb, unsigned merge, CompatInfo * info)
{
ParseCommon *stmt;
if (merge == MergeDefault)
merge = MergeAugment;
info->name = uStringDup(file->name);
stmt = file->defs;
while (stmt)
{
switch (stmt->stmtType)
{
case StmtInclude:
if (!HandleIncludeCompatMap((IncludeStmt *) stmt, xkb, info,
HandleCompatMapFile))
info->errorCount++;
break;
case StmtInterpDef:
if (!HandleInterpDef((InterpDef *) stmt, xkb, merge, info))
info->errorCount++;
break;
case StmtGroupCompatDef:
if (!HandleGroupCompatDef
((GroupCompatDef *) stmt, xkb, merge, info))
info->errorCount++;
break;
case StmtIndicatorMapDef:
{
LEDInfo *rtrn;
rtrn = HandleIndicatorMapDef((IndicatorMapDef *) stmt, xkb,
&info->ledDflt, info->leds, merge);
if (rtrn != NULL)
info->leds = rtrn;
else
info->errorCount++;
}
break;
case StmtVarDef:
if (!HandleInterpVar((VarDef *) stmt, xkb, info))
info->errorCount++;
break;
case StmtVModDef:
if (!HandleVModDef((VModDef *) stmt, merge, &info->vmods))
info->errorCount++;
break;
case StmtKeycodeDef:
ERROR("Interpretation files may not include other types\n");
ACTION("Ignoring definition of key name\n");
info->errorCount++;
break;
default:
WSGO1("Unexpected statement type %d in HandleCompatMapFile\n",
stmt->stmtType);
break;
}
stmt = stmt->next;
if (info->errorCount > 10)
{
#ifdef NOISY
ERROR("Too many errors\n");
#endif
ACTION1("Abandoning compatibility map \"%s\"\n", file->topName);
break;
}
}
return;
}
static void
CopyInterps(CompatInfo * info,
XkbCompatMapPtr compat, Bool needSymbol, unsigned pred)
{
SymInterpInfo *si;
for (si = info->interps; si; si = (SymInterpInfo *) si->defs.next)
{
if (((si->interp.match & XkbSI_OpMask) != pred) ||
(needSymbol && (si->interp.sym == NoSymbol)) ||
((!needSymbol) && (si->interp.sym != NoSymbol)))
continue;
if (compat->num_si >= compat->size_si)
{
WSGO("No room to merge symbol interpretations\n");
ACTION("Symbol interpretations lost\n");
return;
}
compat->sym_interpret[compat->num_si++] = si->interp;
}
return;
}
Bool
CompileCompatMap(XkbFile * file,
XkbFileInfo * result, unsigned merge, LEDInfo ** unboundLEDs)
{
int i;
CompatInfo info;
XkbDescPtr xkb;
GroupCompatInfo *gcm;
xkb = result->xkb;
InitCompatInfo(&info, xkb);
info.dflt.defs.merge = merge;
info.ledDflt.defs.merge = merge;
HandleCompatMapFile(file, xkb, merge, &info);
if (info.errorCount == 0)
{
int size;
if (XkbAllocCompatMap(xkb, XkbAllCompatMask, info.nInterps) !=
Success)
{
WSGO("Couldn't allocate compatibility map\n");
ACTION("Exiting\n");
return False;
}
if (info.name != NULL)
{
if (XkbAllocNames(xkb, XkbCompatNameMask, 0, 0) == Success)
xkb->names->compat =
XkbInternAtom(xkb->dpy, info.name, False);
else
{
WSGO("Couldn't allocate space for compat name\n");
ACTION2("Name \"%s\" (from %s) NOT assigned\n",
scanFile, info.name);
}
}
size = info.nInterps * sizeof(XkbSymInterpretRec);
if (size > 0)
{
CopyInterps(&info, xkb->compat, True, XkbSI_Exactly);
CopyInterps(&info, xkb->compat, True, XkbSI_AllOf | XkbSI_NoneOf);
CopyInterps(&info, xkb->compat, True, XkbSI_AnyOf);
CopyInterps(&info, xkb->compat, True, XkbSI_AnyOfOrNone);
CopyInterps(&info, xkb->compat, False, XkbSI_Exactly);
CopyInterps(&info, xkb->compat, False,
XkbSI_AllOf | XkbSI_NoneOf);
CopyInterps(&info, xkb->compat, False, XkbSI_AnyOf);
CopyInterps(&info, xkb->compat, False, XkbSI_AnyOfOrNone);
}
for (i = 0, gcm = &info.groupCompat[0]; i < XkbNumKbdGroups;
i++, gcm++)
{
if ((gcm->fileID != 0) || (gcm->real_mods != 0)
|| (gcm->vmods != 0))
{
xkb->compat->groups[i].mask = gcm->real_mods;
xkb->compat->groups[i].real_mods = gcm->real_mods;
xkb->compat->groups[i].vmods = gcm->vmods;
}
}
if (info.leds != NULL)
{
if (!CopyIndicatorMapDefs(result, info.leds, unboundLEDs))
info.errorCount++;
info.leds = NULL;
}
ClearCompatInfo(&info, xkb);
return True;
}
if (info.interps != NULL)
uFree(info.interps);
return False;
}