RDM/2 The Russian Electronic Developer Magazine  
RDM/2 Русский электронный журнал разработчика  
ДомойОт редактораПишите намОбратная связьRU/2

Формат файла KEYBOARD.DCP

- Доктор, что у меня ?
- Вскрытие покажет...
(c) народное

От переводчика:
Все началось с того, что в руки моих детей попал редактор раскладок клавиатуры А.Бузанакова (pmlayed/2) из популярного пакета Layer/2. Наигравшись с ним, они начали задавать мне всякие каверзные вопросы, ответы на которые им были долго непонятны, типа: "BG442 - это чья клавиатура? Болгарская? А у них разве не русские буквы? Русские? А это что за черточки на клавиатуре?" Тут я начинал нудно объяснять про кодовые страницы (ладно хоть Display Font Editor уже был готов), говорить, что на самом деле это не черточки, а буквы, только в другой кодировке, и что болгары - вполне нормальные люди и у них на клавишах тоже буквы нарисованы, а не черточки. Отнеслись они к моим объяснениям, понятно, весьма недоверчиво :-) После весьма краткой переписки с автором pmlayed я понял, что не смогу внятно объяснить, что же я хочу. Постепенно окрепло желание сделать это самому, особенно, когда в EDM/2 нашлась данная статья, которую я и использовал при написании своего Редактора Раскладок Клавиатуры (см. "Русский софт"). Потом свои замечания я отправил автору (Martin Lafaix) и он с ними согласился. Поэтому представленный ниже текст отличается от оригинала (желающие могут прочитать его в EDM/2). Ну и конечно, Martin Lafaix дал согласие на перевод статьи и размещение ее в RDM/2.

Введение

Что это такое

Раскладки клавиатур хранятся в файле KEYBOARD.DCP. Эти раскладки используются для преобразования скэн-кодов клавиш (т.е. кодов, полученных от контроллера клавиатуры) в коды соответствующих символов (например, в код символа "A" или в коды виртуальных клавиш, таких как F1 или CapsLock).

Знание формата этого файла может позволить нам изменить существующие или создать новые собственные раскладки клавиатур. Разве это не прекрасно?

Если же мы собираемся написать собственный обработчик клавиатуры (что-нибудь этакое - с преобразованием скэн-кодов в коды символов), то мы тут же ощутим второе преимущество от знания этого формата. В самом деле, зачем изобретать велосипед, самостоятельно расписывая раскладку клавиатуры (довольно скучное занятие - впрочем, на любителя :-)), если у нас уже есть набор готовых раскладок?

От переводчика:
Кстати, это может помочь решить и проблему замены раскладок в PM-сессиях. Статья была написана и опубликована в EDM/2 в 1995 году. Почему никто не воспользовался этой идеей? :-( Впрочем, я хочу и сам попробовать это сделать и, если кто-нибудь может помочь (добрым словом и/или делом), то пишите VicTor Smirnoff.

Содержание

В этой статье описывается формат файла KEYBOARD.DCP операционной системы OS/2. Она состоит из трех частей: первая содержит само описание структуры, во второй рассматриваются вопросы трансляции скэн-кодов в коды символов, а в третьей дано описание программы манипулирования раскладками.
От переводчика:
мы ограничимся переводом первой (наиболее информативной) части.

Благодарности

Первая часть этой статьи в основном базируется на утилите SWAPDCP от Ned Konz, которую можно найти на вашем любимом ftp-сервере.

Формат файла KEYBOARD.DCP

Как организован KEYBOARD.DCP

Первые четыре байта файла KEYBOARD.DCP - это двойное слово, содержащее относительный адрес заголовка таблицы указателей раскладок (в оригинале - Index Table), т.е. его смещение от начала файла.

Заголовок таблицы указателей раскладок выглядит следующим образом:

  typedef struct
  {
    WORD    EntryCount;
    WORD    EntryLength;
  } IndexEntryHeader;
Поле Описание
EntryCount Количество записей в таблице указателей раскладок (т.е. число раскладок в файле).
EntryLength Длина записи таблицы указателей раскладок (всегда равна 18).

Непосредственно за заголовком располагается сама таблица указателей раскладок. Каждая запись в таблице (в оригинале - Index Entry) выглядит следующим образом:

  typedef struct
  {
    BYTE    Country[2];       /* например, "US" */
    BYTE    SubCountryID[4];  /* например, "153 " */
    WORD    word2;
    WORD    XTableID;         /* например, 0x1b5 (437) */
    WORD    KbdType;          /* 0 = 89 кл.   1 = 101/102 кл. */
    ULONG   HeaderLocation;   /* указатель на план раскладки */
    UCHAR   dflag;            /* default translate table for this cp */
    UCHAR   mflag;            /* machine dependent keyboard */
  } IndexEntry;

  #define DFLAG_DEFAULTFORCTRY  0x08 /* Default for country */
                                     /* это означает что раскладка выбирается */
                                     /* в случае, если не указан subcountry, */
                                     /* ну то есть RU, вместо RU441 или RU443 */
  #define DFLAG_MULTILAYERED    0x02 /* Multilayered layout */
  #define DFLAG_SECONDARY       0x04 /* Secondary multilayered layout */
  #define DFLAG_DEFAULTCPAGE    0x01 /* Default codepage for country */
                                     /* означает что это самая раздефолтная */
                                     /* раскладка, т.е. для случая, если даже */
                                     /* неизвестен codepage */

  #define MFLAG_MULTIPLE        0x01 /* Multiple layout for country */
Поле Описание
Country Аббревиатура названия страны ("US", "FR", ...).
Используется обратный порядок байт - таким образом, первый символ аббревиатуры хранится в Country[1], а второй - в Country[0].
SubCountry Идентификатор конкретной раскладки для данной страны ("153", "189", "120", ...). В некоторых странах (в т.ч. и в России) существует несколько основных раскладок клавиатуры (например, RU441 и RU443).
word2 Неизвестно (резерв).
XTableID Кодовая страница, используемая данной раскладкой (437, 850, ...).
KbdType Тип клавиатуры (0 - для 89 клавишных, 1 - для 101/102 клавишных).
HeaderLocation Относительный адрес плана раскладки, т.е. его смещение от начала файла.
dflag Таблица трансляции по умолчанию для данной кодовой страницы
mflag Несколько раскладок для страны

Таким образом, мы можем:

  1. проверить не нарушен ли формат файла KEYBOARD.DCP:

  2. найти заданную раскладку клавиатуры:

План раскладки (в оригинале - Keyboard Layout Table Entry)

Каждый план раскладки содержит заголовок, за которым следуют таблица определения клавиш и таблицы акцентов. Заголовок раскладки выглядит следующим образом:
  typedef struct
  {
    WORD    XTableID;         /* номер кодовой страницы */
 
    /* замечание: поле шириной 32 бита */
    struct
    {
      /* какой Alt или комбинация клавиш используют Char3 в каждом KeyDef */
      BITFIELD    ShiftAlt     :1; /* Shift-Alt вместо Ctrl-Alt */
      BITFIELD    AltGrafL     :1; /* LeftAlt в качестве AltGraphics */
      BITFIELD    AltGrafR     :1; /* RightAlt в качестве AltGraphics */
      /* другие модификаторы */
      BITFIELD    ShiftLock    :1; /* CapsLock рассматривать как ShiftLock */
      BITFIELD    DefaultTable :1; /* предопределенная таблица для языка */
 
      BITFIELD    ShiftToggle  :1; /* TRUE:. временно переключающийся,
                                           FALSE:. сбрасываемый ShiftLock */
 
      BITFIELD    AccentPass   :1; /* TRUE:. передать акцентный символ и пискнуть,
                                           FALSE:. просто пискнуть */
      BITFIELD    CapsShift    :1; /* CapsShift использует Char5 */
      BITFIELD    MachDep      :1; /* аппаратно-зависимая таблица */
      /* модификаторы двунаправленного набора */
      BITFIELD    RTL          :1; /* разрешено переключение между
                                      национальной и английской раскладкой */
      BITFIELD    LangSel      :1; /* TRUE:. национальная раскладка
                                          FALSE:. английская раскладка */
      /* индикатор раскладки, выбираемой по умолчанию */
      BITFIELD    DefaultLayout:1; /* предопределенная раскладка для страны */
    } XTableFlags1;
 
    WORD    KbdType;              /* тип клавиатуры */
    WORD    KbdSubType;           /* подтип клавиатуры */
    WORD    XtableLen;            /* длина плана раскладки */
    WORD    EntryCount;           /* число записей в таблице определения клавиш */
    WORD    EntryWidth;           /* длина записи таблицы определения клавиш */
    BYTE    Country[2];           /* идентификатор страны, например, "US" */
    WORD    TableTypeID;          /* тип таблицы, 0001=OS/2 */
    BYTE    SubCountryID[4];      /* идентификатор раскладки, например, "153 " */
    WORD    Reserved[8];
  } XHeader;
Поле Описание
XTableID Кодовая страница, используемая данной раскладкой.
XTableFlags Флаги раскладки [см. ниже].
KbdType Тип клавиатуры (0 = 89 клавиш, 1 = 101/102 клавиш).
KbdSubType Подтип клавиатуры (??? 0).
XtableLen Длина плана раскладки (в байтах), включая этот заголовок.
EntryCount Количество записей в таблице определения клавиш.
EntryWidth Длина записи определения клавиш (в байтах).
Country Аббревиатура названия страны (используется обратный порядок байт - таким образом вы получите "SU" для US).
TableTypeID Тип таблицы (1 для OS/2).
SubCountryID Идентификатор конкретной раскладки для данной страны ("153 ", "189 ", "120 ", ...).
Reserved Неизвестно.

Следом за этим заголовком располагается таблица определения клавиш из EntryCount записей. Каждая запись таблицы определения клавиш (KeyDef) выглядит следующим образом:

  typedef struct
  {
    WORD    XlateOp;
 
    BYTE    Char1;
    BYTE    Char2;
    BYTE    Char3;
    BYTE    Char4;
    BYTE    Char5;
  } KeyDef;
Поле Описание
XlateOP 9 младших бит определяют тип клавиши [см. ниже].
Старшие 7 бит определяют, какой акцент разрешен для данной клавиши. Младший из этих битов определяет использование первой таблицы акцентов, следующий - второй, и т.д. (если установлен старший 7 бит, то для данного символа разрешено больше 6 акцентов и выборка осуществляется из дополнительных таблиц акцентов [см.ниже]).
Char1 Стандартное значение
Char2 Значение при нажатом Shift (Shifted)
Char3 Значение при нажатом Alt (Alted)
Char4  
Char5  
От переводчика:
Тут, видимо, требуется некое пояснение "что такое акцент и с чем его едят". Так вот, акцентными символами называются символы, имеющие какие-либо украшения (например, символы с тильдочками сверху или крючочками снизу). Сами эти закорючки-украшения называются акцентами и как правило присутствуют в фонтах в виде отдельных символов. И для набора этих символов с клавиатуры обычно используется следующая техника:

  1. на клавиатуре резервируется клавиша (или несколько клавиш) под акценты (значки этих акцентов, как правило, рисуются на клавише для стандартного, Shifted и Alted режимов),
  2. когда вы нажимаете клавишу акцента, то код акцента запоминается и система ждет ввода следующего символа (т.е., скажем для простоты, нажали клавишу, а на экране не отобразился ни какой символ),
  3. если для следующего символа, введенного с клавиатуры, этот акцент разрешен, то в буфер клавиатуры помещается код акцентного символа, выбранный из соответствующей таблицы акцентов (а на экране, соответственно, отображается символ с закорючкой),
  4. иначе в буфер клавиатуры последовательно помещаются коды символа-акцента и этого самого следующего символа (т.е. на экране последовательно появляются - закорючка и в следующем знакоместе этот самый вновь введенный символ).

Содержимое полей charx зависит от значения поля XlateOP (см. [тип клавиши]).
Значение поля EntryWidth (в заголовке) по умолчанию равно 7, но, если это значение больше, то значит в структуре KeyDef присутствуют дополнительные поля charx (т.е. у нас есть EntryWidth - sizeof(XlateOP) полей charx, где sizeof(XlateOP) равно 2).

Следом за таблицей определения клавиш длиной EntryCount записей располагаются шесть стандартных таблиц акцентов (по одной на каждый допустимый акцент). Седьмая таблица акцентов располагается после и может содержать дополнительные таблицы акцентов.

Каждая из 6 стандартных таблиц акцентов выглядит следующим образом:

  typedef struct
  {
    CHAR charOrg;                 /* ASCII значение клавиши, например "e" */
    CHAR charRes;                 /* результирующее ASCII значение, например "ё" */
  } TRANS;
 
  typedef struct
  {
    BYTE AccentGlyph;             /* код символа-акцента (значка акцента) */
    BYTE byte1;
    BYTE byte2;
    BYTE byte3;
    BYTE byte4;
    BYTE byte5;
    TRANS aTrans[20];             /* разрешенные подстановки */
  } AccentTableEntry;
Седьмая таблица акцентов имеет слегка отличный формат. Первый байт этой таблицы содержит длину таблицы. Таким образом, если используется меньше 7 акцентов, то первый байт седьмой таблицы содержит значение 0x00. Если же используется больше 6 акцентов, то седьмая таблица акцентов может представлять из себя комбинацию дополнительных акцентных таблиц, построенных следующим образом:

  1. дополнительные таблицы следуют одна за другой,
  2. первый байт каждой таблицы содержит ее длину.
  +--+-------------+--+----------------+--+------------
  |17| таблица #7  |11| таблица #8     |19| таблица #9
  +--+-------------+--+----------------+--+------------
  <-- 17 байт ----><-- 11 байт -------><-- 19 байт ----
Структура этих дополнительных таблиц аналогична структуре стандартной таблицы. Количество элементов в массиве разрешенных подстановок определяется из длины этой дополнительной таблицы.

Индикатор "конец таблицы" здесь отсутствует. Для его вычисления можно воспользоваться следующим методом:

  1. длина области, занимаемой всеми таблицами акцентов,

    AccentTableEntryLen = XTableLen - sizeof(XHeader) - EntryCount * EntryWidth,

  2. первые шесть стандартных таблиц акцентов занимают

    6*sizeof(AccentTableEntry) = 276 байт,

  3. оставшиеся таблицы акцентов занимают

    AccentTableEntryLen - 276 байт,

  4. сумма длин всех дополнительных таблиц акцентов (т.е. l7 + l8 + ...) должна быть равна этому значению.

Таким образом, процесс получения акцентных символов из комбинации нажатий "акцент+символ" достаточно прост:

  1. если нажата клавиша акцента достаточно запомнить номер акцента (соответствующий charx из KeyDef),
  2. затем ждем нажатия следующей клавиши,
  3. если для этой клавиши разрешен данный акцент (установлен соответствующий бит в XlateOP), то по номеру акцента выбираем таблицу акцентов, ищем совпадающий charOrg в массиве aTrans и возвращаем соответствующий charRes,
  4. если же данный акцент запрещен или не найден правильный charOrg, то просто пискнуть, если флаг AccentPass сброшен, если же флаг AccentPass установлен, то по номеру акцента выбрать таблицу акцентов и возвратить AccentGlyph и код вновь введенного символа.
Все, готово!

Флаги раскладки

Флаги определяют поведение клавиатуры.

Флаг Описание
ShiftAlt Когда этот флаг установлен в 1, то комбинация "Shift+Alt" используется вместо комбинации "Ctrl+Alt" для получения alted-значения клавиши (для 89 клавишных клавиатур).
AltGrL Когда этот флаг установлен в 1, то левая клавиша "Alt" используется в качестве клавиши "AltGr" (для 101/102 клавишных клавиатур).
AltGrR Когда этот флаг установлен в 1, то правая клавиша "Alt" используется в качестве клавиши "AltGr" (для 101/102 клавишных клавиатур).
ShiftLock Когда этот флаг установлен в 1, то клавиша "CapsLock" работает в качестве клавиши "ShiftLock". Таким образом, если CapsLock установлен в ON, то нажатие клавиши "Shift" сбрасывает это состояние.
Если флаг сброшен в 0, то нажатие клавиши "Shift" временно переводит CapsLock в состояние OFF; состояние ON восстанавливается после отпускания клавиши "Shift".
DefaultTable Когда этот флаг установлен в 1, то данная раскладка используется для данной кодовой странице в данной стране по умолчанию.
ShiftToggle Для 89-клавишных клавиатур этот флаг устанавливается в зависимости от состояния флага ShiftLock. Если ShiftLock равен 1, то ShiftToggle сбрасывается в 0. И наоборот, если ShiftLock равен 0, то ShiftToggle устанавливается равным 1.
Для 101/102-клавишных клавиатур этот флаг всегда сброшен в 0.
AccentPass Когда этот флаг установлен в 1, то клавиши, для которых запрещен данный акцент, формируют последовательность из кода значка акцента и кода символа.
CapsShift Когда этот флаг установлен в 1 и если CapsLock находится в состоянии ON, то в качестве Shifted- значения используется char5, а не char3.
Всегда установлен в 1 для всех клавиатур Swiss (Швейцария???) и 0 для всех остальных.
MachDep Этот флаг устанавливается в 1, если в данной стране для данной кодовой страницы используется больше, чем одна физическая раскладка клавиатуры. См. описание флага DefaultLayout ниже.
RTL Когда этот флаг установлен в 1, то данная раскладка используется при переключении клавиатуры между национальной и английской раскладками. (Martin Lafaix назвал это поле RTL, сокращенно от Right-to-Left, т.е. набор справа налево. Но этот флаг равен 1 не только для арабского и иврита, но и для греческого языка, для всех языков, использующих кириллицу, для языков прибалтийских народов и т.д., т.е. для всех языков, где используется переключение клавиатуры между национальной и английской частями).
LangSel Если флаг равен 1, то это национальная раскладка (используется национальный алфавит), если равен 0 - английская раскладка.
Этот флаг неиспользуется и должен быть равен 0, если RTL равен 0.
DefaultLayout Если MachDep равен 1, то установка этого флага в 1 сигнализирует, что данная раскладка используется по умолчанию. Иначе должен быть равен 0.

Тип клавиши

Тип клавиши (младшие 9 битов XlateOP) определяет использование полей charx из KeyDef.
Замечание:
В следующей таблице, понятие "xxx'ed'" означает удерживание в нажатом положении клавиши xxx во время нажатия другой клавиши (например, Shifted означает нажатие в то время, как была нажата и удерживалась в нажатом положении клавиша "Shift").
Если в описании присутствует "???", то это означает неполноту описания и/или неувереность в его правильности...
От переводчика:
Здесь требуется еще одно пояснение, касающееся акцентов.
Когда говорится, что поля char1, char2 или char3 содержат акцент, то это означает, что в этом поле находится двоичное значение от 1 до 7, представляющее из себя номер акцента, т.е. номер таблицы акцентов.
Если какое-либо из этих полей содержит значение акцента равное 7 (а неплохо звучит - "седьмой акцент", почти как "седьмая печать" :-)), то реальный номер акцента находится в поле char5 (т.е. 7, 8 и т.д.).
Как всегда, не обходится без исключений - в раскладках LT456 (CP921 нац) и LV455 (CP921 нац) клавиша акцента (тип 0x0B) в char1, char2 и char3 содержит одно и то же значение равное 7, а char5 в обоих случаях равен -1 (т.е. 0xFF). По моему мнению (возможно ошибочному) в этом случае при выборе акцента в стандартном режиме используется акцентная таблица 7, в Shifted режиме - таблица 8 и в Alted - 9. Смущает так же то, что клавиша акцента есть, но нет ни одной клавиши, для которой был бы разрешен хоть один какой-нибудь акцент. Хотя седьмая таблица акцентов присутствует в необходимом комплекте.
Тип Описание
0x00 EmptyKey.
Нет клавиш, генерирующих этот скэн-код.
0x01 AlphaKey.
Алфавитная клавиша.
Поле char1 содержит стандартный код символа.
Поле char2 содержит Shifted код символа.
Если акцентные биты не 0, то они определяют разрешенные акценты.
Каждая алфавитная клавиша может генерировать Ctrl'ed код символа, если нажата совместно с Ctrl. В этом случае генерируется код символа равный char1-96.
0x02 SpecKey.
Эта клавиша генерирует стандартный (char1) и Shifted (char2) коды.
Она не может генерировать Alt'ed, AltGr'ed или Ctrl'ed коды.
0x03 SpecKeyC.
Эта клавиша может генерировать AltGr'ed код (содержится в char3).
Если char3 содержит 0, AltGr'ed код не генерируется.
Значение в char3 в пределах 1...7, считается акцентом.
Иначе это обычный символ.
Поля char1 и char2 содержат стандартный и Shifted коды, соответственно. Если CapsLock находится в состоянии ON, то назначение полей меняется: char1 - Shifted код, char2 - стандартный.
0x04 SpecKeyA.
Эта клавиша может генерировать AltGr'ed код (содержится в char3).
Поля char1 и char2 содержат стандартный и Shifted коды, соответственно, независимо от состояния CapsLock.
Значение char3 в пределах 0...31 считается управляющим кодом (даже в пределах 1...7, в отличие от SpecKeyC).
0x05 SpecKeyCA ???
0x06 FuncKey.
Функциональная клавиша.
Поле char1 содержит номер функциональной клавиши.
Остальные поля равны 0.
0x07 PadKey.
Клавиша дополнительной цифровой клавиатуры.
Поле char1 содержит информацию о клавише (0 = "7", 1="8", 2="9", 3="-", 4="4", 5="5", 6="6", 7="+", 8="1", 9="2", 10="3", 11="0" и 12 = ".").
Замечание: соответствует раскладке старых (89 клавишных) клавиатур.
Поле char2 содержит ASCII символ.
Все остальные поля равны 0.
0x08 SpecCtlKey.
Эта клавиша генерирует управляющие коды (ASCII-коды в границах 0...31).
Поля char1 и char2 содержат стандартный и Shifted управляющие коды, соответственно.
Все остальные поля равны 0.
0x09 Клавиша PrtScrn.
0x0A Клавиша SysReq
0x0B AccentKey.
Эта клавиша генерирует коды акцента (в пределах 1...7).
Поля char1, char2 и char3 содержат стандартный, Shifted и Alted акценты, соответственно.
Значение поля char5 зависит от содержимого полей char1, char2 и char3. Если какое-либо из них равно 7, то в char5 находится реальный акцент (7, 8, ...) и выборка для данного акцента осуществляется из дополнительных таблиц седьмого акцента (есть исключения [см. выше]).
Иначе char5 равно char1.
Поле char4 равно 0.
0x0C ShiftKey.
Клавиша Shift или Ctrl.
Если char1 равно 0x01, то это правый Shift.
Если char1 равно 0x02, то это левый Shift.
Поля char2...char5 равны 0.
Если char1 равно 0x04, то это Ctrl (при этом char2 равен 0x01, а char3 равен 0x04). Поля char4...char5 равны 0.
0x0D ToggleKey ???
0x0E Клавиша Alt.
0x0F Клавиша NumLock.
0x10 Клавиша CapsLock.
0x11 Клавиша ScrollLock.
0x12 XShiftKey ???
0x13 XToggleKey ???
0x14 SpecKeyCS ???
Аналогично SpecKeyC (тип 0x03).
0x15 SpecKeyAS ???
Аналогично SpecKeyC (тип 0x03).
0x1A ExtExtKey.
Новые курсорные клавиши.
Отсутствуют на 89-клавишных клавиатурах.
<другие> Неизвестно.

Вот и все, что можно сказать о формате файла KEYBOARD.DCP.

От переводчика:
далее автор описывает некоторые алгоритмы преобразования скэн-кодов в коды символов и приемы работы с утилитой SWAPDCP.

Я не счел необходимым переводить эти части. Желающие могут обратиться к оригиналу.

И напоследок хочу поблагодарить:

  1. Martin Lafaix - за статью,
  2. Дмитрия Стекленева и Андрея Бузанакова - за layer/2
  3. и всех, кто дочитал до конца.
Автор: Martin Lafaix
Переводчик: VicTor Smirnoff

---
Интересные ссылки:

---

---
Комментариев к странице: 0 | Добавить комментарий
---
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов
(C) Russian Underground/2